From: Junye Ji io_ring_buffers_peek() saves the incoming iovec array in org_iovs, then may replace arg->iovs while expanding a provided-buffer bundle. With KBUF_MODE_FREE set, the success path frees arg->iovs instead of org_iovs. It either frees a replacement before the caller uses it or, when no replacement was needed, frees the cache still owned by the request. Cleanup later frees the same cache again. I reproduced the stale read and double free on the no-growth and successful-growth paths with two completions from one multishot receive. The fixed KASAN kernel completed 100 runs of both triggers and both liburing bundle tests without a report. Fixes: cd053d788c3f ("io_uring: fix dangling iovec after provided-buffer bundle grow failure") Assisted-by: Codex-Security:unspecified Signed-off-by: Junye Ji --- I can send the reproducer and full trace/KASAN logs privately if needed. --- io_uring/kbuf.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/io_uring/kbuf.c b/io_uring/kbuf.c index b6b969b55e12..07d81dc7cbe2 100644 --- a/io_uring/kbuf.c +++ b/io_uring/kbuf.c @@ -328,8 +328,8 @@ static int io_ring_buffers_peek(struct io_kiocb *req, struct buf_sel_arg *arg, buf = io_ring_head_to_buf(br, ++head, bl->mask); } while (--nr_iovs); - if (arg->mode & KBUF_MODE_FREE) - kfree(arg->iovs); + if ((arg->mode & KBUF_MODE_FREE) && arg->iovs != org_iovs) + kfree(org_iovs); if (head == tail) req->flags |= REQ_F_BL_EMPTY; --- base-commit: 44696aa3a489d2baf58efa61b37833f100072bee change-id: 20260712-io-uring-kbuf-iovec-lifetime-4ff406a0b0c3 Best regards, -- Junye Ji