[BUG] io_uring: 13 remaining unprotected ctx->rings accesses after 61a11cf481272
From: asaf meizner
Date: Thu Apr 09 2026 - 05:22:57 EST
Hi Jens,
Commit 61a11cf481272 ("io_uring: protect remaining lockless ctx->rings
accesses with RCU") introduced RCU protection for ctx->rings during
ring resize, but at least 13 dereferences were not converted. The
most concerning is fdinfo.c:63 which reads ctx->rings with no lock
and no RCU protection at all.
Unprotected accesses found:
io_uring/fdinfo.c:63
struct io_rings *r = ctx->rings;
(no lock, no RCU — TOCTOU with concurrent resize)
io_uring/tw.c:41, 253, 291, 326
atomic_andnot/atomic_or on ctx->rings->sq_flags
(lines 249-253 have a comment justifying no RCU for
!DEFER_TASKRUN, but lines 41 and 291 have no such comment
and appear to be in contexts where resize could race)
io_uring/sqpoll.c:380, 407, 421
atomic_or/atomic_andnot on ctx->rings->sq_flags
(under sqd->lock, but resize takes uring_lock — different locks)
io_uring/io_uring.c:574, 647, 690, 1985, 1986
CQ overflow flags and sq_dropped counter
The fdinfo case is the clearest bug: a read of
/proc/<pid>/fdinfo/<fd> concurrent with
IORING_REGISTER_RESIZE_RINGS can dereference a stale ctx->rings
pointer after the old rings are freed via RCU. This is a UAF read
that could leak kernel heap data.
The sqpoll case is also concerning because sqd->lock and uring_lock
are different locks, so the SQPOLL thread can see a stale pointer
during resize.
Minimal fix for fdinfo:
void io_uring_show_fdinfo(struct io_ring_ctx *ctx)
{
- struct io_rings *r = ctx->rings;
+ struct io_rings *r;
+ rcu_read_lock();
+ r = rcu_dereference(ctx->rings);
/* ... use r ... */
+ rcu_read_unlock();
}
I haven't written a full patch for all 13 sites because the right
fix depends on whether io_get_rings() or raw rcu_read_lock() is
preferred for each site, and some of the tw.c accesses may be
intentionally unprotected for !DEFER_TASKRUN. Happy to write the
full patch if you can clarify which sites actually need fixing.
Reproducer:
Thread A: cat /proc/$(pidof target)/fdinfo/$(target_uring_fd)
Thread B: io_uring_register(fd, IORING_REGISTER_RESIZE_RINGS, ...)
Tested against: current HEAD (7.0-rc series)
Thanks,
Asaf Meizner