Linux io-uring development
 help / color / mirror / Atom feed
* [PATCH] io_uring/kbuf: free the old cached iovec, not the returned one, on bundle grow
@ 2026-07-13 18:31 Doruk Tan Ozturk
  2026-07-14 14:02 ` Gabriel Krisman Bertazi
  0 siblings, 1 reply; 3+ messages in thread
From: Doruk Tan Ozturk @ 2026-07-13 18:31 UTC (permalink / raw)
  To: axboe; +Cc: io-uring, Doruk Tan Ozturk

Commit cd053d788c3f ("io_uring: fix dangling iovec after provided-buffer
bundle grow failure") moved the KBUF_MODE_FREE kfree() out of the expand
branch to after the validation loop, so the old cached iovec is only
released once the new buffers have been validated. However, by the time
control reaches the post-loop free, arg->iovs has already been reassigned
in the expand branch to the freshly allocated array that is about to be
returned to the caller:

	iov = kmalloc_objs(struct iovec, nr_avail);
	...
	arg->iovs = iov;		/* now the array we return */
	...
	if (arg->mode & KBUF_MODE_FREE)
		kfree(arg->iovs);	/* ... but this frees it */

On a successful grow, io_ring_buffers_peek() therefore frees the very
iovec array it returns. io_recv_buf_select() then builds an iov_iter over
that freed array and caches it in kmsg->vec.iovec, giving a
slab-use-after-free read during the recv copy and a later double free of
the iovec array on request cleanup. The array is a kmalloc() whose size is
controlled by the number of ring buffers the caller commits, so the freed
object lands in an attacker-influenced kmalloc cache.

KBUF_MODE_FREE is meant to release the *old* cached iovec once it has been
replaced by a larger one. Free the captured org_iovs instead, and only
when a grow actually happened (arg->iovs != org_iovs) so the no-grow case
still returns the reused array. The -EFAULT failure path already frees the
new array and leaves org_iovs for the caller, so it is unaffected.

Reproduced on next-20260710 with KASAN by an unprivileged IORING_OP_RECV
using IORING_RECVSEND_BUNDLE over a provided-buffer ring: a first
(expanding) bundle caches a small iovec, and an in-request bundle retry
grows again under KBUF_MODE_FREE, triggering both the UAF read and the
double free. The change eliminates the KASAN splat.

Fixes: cd053d788c3f ("io_uring: fix dangling iovec after provided-buffer bundle grow failure")
Signed-off-by: Doruk Tan Ozturk <doruk@0sec.ai>
---
 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;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 3+ messages in thread
* Re: [PATCH] io_uring/kbuf: free the old cached iovec, not the returned one, on bundle grow
@ 2026-07-14 14:14 Doruk Tan Ozturk
  0 siblings, 0 replies; 3+ messages in thread
From: Doruk Tan Ozturk @ 2026-07-14 14:14 UTC (permalink / raw)
  To: krisman; +Cc: axboe, io-uring

> Aren't LLMs fun?

Ha — they are indeed. Beat me to it by a day. Please drop mine.

Best,
Doruk

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-07-14 14:14 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-13 18:31 [PATCH] io_uring/kbuf: free the old cached iovec, not the returned one, on bundle grow Doruk Tan Ozturk
2026-07-14 14:02 ` Gabriel Krisman Bertazi
  -- strict thread matches above, loose matches on Subject: below --
2026-07-14 14:14 Doruk Tan Ozturk

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox