From a3bdf68feecc57af5c11fb599f860ac9790ffad9 Mon Sep 17 00:00:00 2001 From: Yao Kai Date: Wed, 23 Sep 2026 09:43:10 +0800 Subject: [PATCH 1/9] io_uring: initialize task context before running the BPF loop Submitting SQEs through an io_uring BPF loop can trigger a NULL pointer dereference in io_submit_sqes(), as shown by the following arm64 report: [ 2049.380301] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000 [ 2049.388788] pc : io_submit_sqes+0x48/0x6c4 [ 2049.389153] lr : bpf_io_uring_submit_sqes+0x10/0x1c [ 2049.396310] Call trace: [ 2049.396531] io_submit_sqes+0x48/0x6c4 (P) [ 2049.396893] bpf_io_uring_submit_sqes+0x10/0x1c [ 2049.397293] bpf_prog_1a2a95cb199202b5+0x3c/0x68 [ 2049.397699] bpf__io_uring_bpf_ops_loop_step+0x5c/0x94 [ 2049.398149] io_run_loop+0x70/0x2e4 [ 2049.398461] __arm64_sys_io_uring_enter+0xf0/0x710 [ 2049.398882] invoke_syscall+0x54/0x10c [ 2049.399414] el0_svc_common.constprop.0+0x40/0xe0 [ 2049.399829] do_el0_svc+0x1c/0x28 [ 2049.400125] el0_svc+0x38/0x1d0 [ 2049.400409] el0t_64_sync_handler+0xa0/0xe4 [ 2049.400779] el0t_64_sync+0x198/0x19c io_uring_enter() dispatches to io_run_loop() before reaching the normal submission path's io_uring_add_tctx_node() call. The loop only checks whether the caller is allowed to run task work; it does not initialize current->io_uring or associate the task with the ring. A BPF call to bpf_io_uring_submit_sqes() then reaches io_get_task_refs(), which assumes that current->io_uring is valid. A ring created with IORING_SETUP_R_DISABLED, IORING_SETUP_SINGLE_ISSUER and IORING_SETUP_DEFER_TASKRUN can be enabled by a different task that has never used io_uring. Enabling the ring makes that task the submitter without allocating its io_uring task context. Its first BPF-driven submission of a pending SQE can therefore cause a kernel Oops. Call io_uring_add_tctx_node() in io_run_loop() before invoking the loop and propagate any initialization error. Do this before acquiring uring_lock, since task context setup may acquire the same mutex. This also establishes the task-to-ring association used for cancellation. Fixes: 033af2b3eb19 ("io_uring: introduce callback driven main loop") Signed-off-by: Yao Kai Link: https://patch.msgid.link/20260923014310.591292-1-yaokai34@huawei.com Signed-off-by: Jens Axboe --- io_uring/loop.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/io_uring/loop.c b/io_uring/loop.c index bbbb6ef14e6a..3d472b7827fa 100644 --- a/io_uring/loop.c +++ b/io_uring/loop.c @@ -2,6 +2,7 @@ #include "io_uring.h" #include "wait.h" #include "loop.h" +#include "tctx.h" static inline int io_loop_nr_cqes(const struct io_ring_ctx *ctx, const struct iou_loop_params *lp) @@ -81,6 +82,10 @@ int io_run_loop(struct io_ring_ctx *ctx) { int ret; + ret = io_uring_add_tctx_node(ctx); + if (unlikely(ret)) + return ret; + if (!io_allowed_run_tw(ctx)) return -EEXIST; From 98ad5b63f5041dc354a6e694f4a9436f74e0681b Mon Sep 17 00:00:00 2001 From: Junyuan Feng Date: Wed, 23 Sep 2026 09:20:44 +0000 Subject: [PATCH 2/9] io_uring/zcrx: requeue multishot receives stopped by a local resource A multishot RECV_ZC can go idle with unread TCP data when a receiver-local resource runs out. An empty copy-fallback niov freelist returns -ENOMEM; a full CQ returns -ENOSPC. After partial progress the stop reason is lost: io_zcrx_copy_chunk() and io_zcrx_recv_skb() report the copied bytes, and tcp_read_sock() drops an error from a later call once earlier data was consumed. The edge-triggered request is then not requeued, so it cannot consume the remaining data until another socket event arrives. Record in io_zcrx_recv_skb() when a walk stops before consuming the length it was offered, and requeue if the pass still returned data. The failed chunk is first on the next pass. The resource is usually still exhausted by then, so that pass fails at once and ends the request with an error CQE, which the application must handle by re-arming. The request does not spin, and the stall becomes a visible error. Keep the existing skb limit and SOCK_DONE handling. Fixes: 11ed914bbf94 ("io_uring/zcrx: add io_recvzc request") Fixes: bc57c7d36c4c ("io_uring/zcrx: add copy fallback") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-6-sol Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Junyuan Feng Reviewed-by: Pavel Begunkov Link: https://patch.msgid.link/010001a0cd914529-23df2f94-bbe0-49cd-ae5d-05922356fa12-000000@email.amazonses.com Signed-off-by: Jens Axboe --- io_uring/zcrx.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index 1b3b11405dac..beb35077f3ce 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -374,6 +374,7 @@ struct io_zcrx_args { struct io_kiocb *req; struct io_zcrx_ifq *ifq; unsigned nr_skbs; + bool stopped_early; }; static const struct memory_provider_ops io_uring_pp_zc_ops; @@ -1899,6 +1900,9 @@ io_zcrx_recv_skb(read_descriptor_t *desc, struct sk_buff *skb, } out: + /* Bytes left in len mean an error stopped the walk early. */ + if (len) + args->stopped_early = true; if (offset == start_off) return ret; desc->count -= (offset - start_off); @@ -1935,8 +1939,9 @@ static int io_zcrx_tcp_recvmsg(struct io_kiocb *req, struct io_zcrx_ifq *ifq, ret = -ENOTCONN; else ret = -EAGAIN; - } else if (unlikely(args.nr_skbs > IO_SKBS_PER_CALL_LIMIT) && - (issue_flags & IO_URING_F_MULTISHOT)) { + } else if ((issue_flags & IO_URING_F_MULTISHOT) && + (unlikely(args.nr_skbs > IO_SKBS_PER_CALL_LIMIT) || + args.stopped_early)) { ret = IOU_REQUEUE; } else if (sock_flag(sk, SOCK_DONE)) { /* Make it to retry until it finally gets 0. */ From 3a3d93070c5ec8b8049d57025987ba313420bda9 Mon Sep 17 00:00:00 2001 From: lollipopkit Date: Sun, 27 Sep 2026 15:42:06 +0800 Subject: [PATCH 3/9] io_uring/cmd_net: end TX_TIMESTAMP multishot when the CQ is full SOCKET_URING_OP_TX_TIMESTAMP arms an EPOLLERR apoll multishot and, on each error queue edge, posts one 32 byte aux CQE per timestamp skb. Aux CQEs have no overflow backing, so io_uring_cmd_post_mshot_cqe32() fails once the CQ ring is full. The loop then stops, splices the unprocessed skbs back onto sk_error_queue and returns -EAGAIN. -EAGAIN is IOU_RETRY, so the request goes idle until the next poll event. io_arm_apoll() forces EPOLLET and draining the CQ does not re-run the command, so the spliced-back timestamps stay queued until an unrelated later timestamp produces a new edge. End the multishot when a CQE cannot be posted, as multishot poll and recv already do. There is no partial result to report, so complete with -ENOBUFS: no CQ space is left for the timestamp CQEs. This command does not use provided buffers, so the value cannot be confused with a buffer ring running empty. The terminal CQE still reaches userspace because request completions can go to the overflow list. The application sees a CQE without IORING_CQE_F_MORE, which it already has to handle, and the first issue of the re-armed request delivers the queued timestamps. A timestamp that cannot be extracted keeps its current behaviour. This was found with LLM assistance while reviewing io_uring multishot handlers for the pattern behind a RECV_ZC stall reported earlier (see Link), and confirmed with a reproducer on v7.3-rc2 and an A/B run of this patch. Fixes: 9e4ed359b8ef ("io_uring/netcmd: add tx timestamping cmd support") Link: https://lore.kernel.org/all/010001a0cd914529-23df2f94-bbe0-49cd-ae5d-05922356fa12-000000@email.amazonses.com/ Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-6-sol Assisted-by: Claude:claude-opus-5-5 Signed-off-by: lollipopkit Reviewed-by: Pavel Begunkov Link: https://patch.msgid.link/20260927074206.65427-1-a@lolli.tech Signed-off-by: Jens Axboe --- io_uring/cmd_net.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/io_uring/cmd_net.c b/io_uring/cmd_net.c index 7cd411fc4f33..90d4ec7cc761 100644 --- a/io_uring/cmd_net.c +++ b/io_uring/cmd_net.c @@ -69,8 +69,8 @@ static inline int io_uring_cmd_setsockopt(struct socket *sock, optlen); } -static bool io_process_timestamp_skb(struct io_uring_cmd *cmd, struct sock *sk, - struct sk_buff *skb, unsigned issue_flags) +static int io_process_timestamp_skb(struct io_uring_cmd *cmd, struct sock *sk, + struct sk_buff *skb, unsigned int issue_flags) { struct sock_exterr_skb *serr = SKB_EXT_ERR(skb); struct io_uring_cqe cqe[2]; @@ -83,7 +83,7 @@ static bool io_process_timestamp_skb(struct io_uring_cmd *cmd, struct sock *sk, ret = skb_get_tx_timestamp(skb, sk, &ts); if (ret < 0) - return false; + return ret; tskey = serr->ee.ee_data; tstype = serr->ee.ee_info; @@ -98,7 +98,9 @@ static bool io_process_timestamp_skb(struct io_uring_cmd *cmd, struct sock *sk, iots = (struct io_timespec *)&cqe[1]; iots->tv_sec = ts.tv_sec; iots->tv_nsec = ts.tv_nsec; - return io_uring_cmd_post_mshot_cqe32(cmd, issue_flags, cqe); + if (!io_uring_cmd_post_mshot_cqe32(cmd, issue_flags, cqe)) + return -ENOBUFS; + return 0; } static int io_uring_cmd_timestamp(struct socket *sock, @@ -135,7 +137,8 @@ static int io_uring_cmd_timestamp(struct socket *sock, skb = skb_peek(&list); if (!skb) break; - if (!io_process_timestamp_skb(cmd, sk, skb, issue_flags)) + ret = io_process_timestamp_skb(cmd, sk, skb, issue_flags); + if (ret) break; __skb_dequeue(&list); consume_skb(skb); @@ -145,6 +148,12 @@ static int io_uring_cmd_timestamp(struct socket *sock, scoped_guard(spinlock_irqsave, &q->lock) skb_queue_splice(&list, q); } + /* + * Aux CQEs cannot overflow and the poll is edge triggered, so nothing + * re-runs the command once the CQ drains. End the multishot instead. + */ + if (ret == -ENOBUFS) + return -ENOBUFS; return -EAGAIN; } From a92193c91e8839fe5fc209616ea95465ae4b919d Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 29 Sep 2026 06:52:17 -0600 Subject: [PATCH 4/9] io_uring: fix task_work add use-after-free with SQPOLL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For SQPOLL, the sqpoll thread runs task_work off its own list and doesn't need to be told about it, so it can pop and complete a request as soon as mpscq_push() has published it. If that was the last request holding the ring alive, io_ring_exit_work() then frees the ctx, stops the thread, and with it the tctx, while io_req_normal_work_add() is still looking at both after the push: BUG: KASAN: slab-use-after-free in io_req_normal_work_add+0x439/0x510 Read of size 4 at addr ff11000000ca0000 by task repro/55 (...) BUG: KASAN: slab-use-after-free in queue_work_on+0x25/0x70 Write of size 8 at addr ff11000000c3bd00 by task repro/55 Hold an RCU read lock across the add, like io_req_local_work_add() already does, and have io_ring_exit_work() wait for a grace period for SQPOLL rings as well before freeing the ctx. Reported-by: Jérémy Jean Link: https://lore.kernel.org/io-uring/20260924205056.3759980-2-Jeremy.Jean@oss.cyber.gouv.fr/ Fixes: af5d68f8892f ("io_uring/sqpoll: manage task_work privately") Cc: stable@vger.kernel.org Signed-off-by: Jens Axboe --- io_uring/io_uring.c | 4 ++-- io_uring/tw.c | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index 2cb9b58d04dd..c2ce83c7c1f1 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -2405,8 +2405,8 @@ static __cold void io_ring_exit_work(struct work_struct *work) spin_lock(&ctx->completion_lock); spin_unlock(&ctx->completion_lock); - /* pairs with RCU read section in io_req_local_work_add() */ - if (ctx->flags & IORING_SETUP_DEFER_TASKRUN) + /* pairs with the RCU read sections in the task_work add paths */ + if (ctx->flags & (IORING_SETUP_DEFER_TASKRUN | IORING_SETUP_SQPOLL)) synchronize_rcu(); io_ring_ctx_free(ctx); diff --git a/io_uring/tw.c b/io_uring/tw.c index f573bcc3af6a..f9affd68dd26 100644 --- a/io_uring/tw.c +++ b/io_uring/tw.c @@ -210,6 +210,9 @@ void io_req_normal_work_add(struct io_kiocb *req) struct io_uring_task *tctx = req->tctx; struct io_ring_ctx *ctx = req->ctx; + /* SQPOLL can retire the request on push, see io_ring_exit_work() */ + guard(rcu)(); + /* tw run already pending, nothing else to do */ if (!mpscq_push(&tctx->task_list, &req->io_task_work.node)) return; From b59cabf9b4acb65f3f9968087ca0e18010d85076 Mon Sep 17 00:00:00 2001 From: Hengyu Liang Date: Tue, 6 Oct 2026 08:57:32 -0400 Subject: [PATCH 5/9] io_uring: do not charge the SQ/CQ rings to RLIMIT_MEMLOCK Commit 8078486e1d53 ("io_uring: use region api for SQ") and commit 81a4058e0cd0 ("io_uring: use region api for CQ") made io_uring_setup() allocate the rings with io_create_region(). However, io_create_region() charges the memory to RLIMIT_MEMLOCK, and the rings had been exempt from that limit since commit 26bfa89e25f4 ("io_uring: place ring SQ/CQ arrays under memcg memory limits"). As of now, a user without CAP_IPC_LOCK gets ENOMEM from io_uring_setup() when their rings exceed the limit, which is 8 MiB by default. PostgreSQL developers have already hit this in their io_uring tests [1]. The issue can be reproduced with a simple liburing program, run as an unprivileged user: #include #include int main(void) { static struct io_uring ring[64]; int i; for (i = 0; i < 64; i++) if (io_uring_queue_init(4096, &ring[i], 0) < 0) break; printf("%d rings\n", i); return 0; } Before those commits (v6.13), it prints "64 rings". After those commits (v6.14), it prints "21 rings". This patch makes io_create_region() take the user to charge, and passes no user for the SQ/CQ rings. Link: https://www.postgresql.org/message-id/flat/667f7dcf-404b-4898-a426-57ca6e0617f6@gmail.com [1] Fixes: 8078486e1d53 ("io_uring: use region api for SQ") Fixes: 81a4058e0cd0 ("io_uring: use region api for CQ") Cc: stable@vger.kernel.org Signed-off-by: Hengyu Liang Link: https://lore.kernel.org/io-uring/20261006125732.3425762-1-hengyul@cs.unc.edu/ Signed-off-by: Jens Axboe --- io_uring/io_uring.c | 10 ++++++---- io_uring/kbuf.c | 2 +- io_uring/memmap.c | 10 +++++----- io_uring/memmap.h | 2 +- io_uring/register.c | 12 +++++++----- io_uring/zcrx.c | 2 +- 6 files changed, 21 insertions(+), 17 deletions(-) diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index c2ce83c7c1f1..79c40e0abf17 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -2070,8 +2070,9 @@ int io_submit_sqes(struct io_ring_ctx *ctx, unsigned int nr) static void io_rings_free(struct io_ring_ctx *ctx) { - io_free_region(ctx->user, &ctx->sq_region); - io_free_region(ctx->user, &ctx->ring_region); + /* ring memory is not charged to RLIMIT_MEMLOCK, hence no user */ + io_free_region(NULL, &ctx->sq_region); + io_free_region(NULL, &ctx->ring_region); ctx->rings = NULL; RCU_INIT_POINTER(ctx->rings_rcu, NULL); ctx->sq_sqes = NULL; @@ -2735,7 +2736,8 @@ static __cold int io_allocate_scq_urings(struct io_ring_ctx *ctx, rd.user_addr = p->cq_off.user_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx, &ctx->ring_region, &rd, IORING_OFF_CQ_RING); + /* ring memory is not charged to RLIMIT_MEMLOCK, hence no user */ + ret = io_create_region(NULL, &ctx->ring_region, &rd, IORING_OFF_CQ_RING); if (ret) return ret; ctx->rings = rings = io_region_get_ptr(&ctx->ring_region); @@ -2749,7 +2751,7 @@ static __cold int io_allocate_scq_urings(struct io_ring_ctx *ctx, rd.user_addr = p->sq_off.user_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx, &ctx->sq_region, &rd, IORING_OFF_SQES); + ret = io_create_region(NULL, &ctx->sq_region, &rd, IORING_OFF_SQES); if (ret) { io_rings_free(ctx); return ret; diff --git a/io_uring/kbuf.c b/io_uring/kbuf.c index 7c309173dd19..7c59ab9cfc8b 100644 --- a/io_uring/kbuf.c +++ b/io_uring/kbuf.c @@ -676,7 +676,7 @@ int io_register_pbuf_ring(struct io_ring_ctx *ctx, void __user *arg) rd.user_addr = reg.ring_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx, &bl->region, &rd, mmap_offset); + ret = io_create_region(ctx->user, &bl->region, &rd, mmap_offset); if (ret) goto fail; br = io_region_get_ptr(&bl->region); diff --git a/io_uring/memmap.c b/io_uring/memmap.c index 48c0eb012412..d4d716946971 100644 --- a/io_uring/memmap.c +++ b/io_uring/memmap.c @@ -204,7 +204,7 @@ static int io_region_allocate_pages(struct io_mapped_region *mr, return 0; } -int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, +int io_create_region(struct user_struct *user, struct io_mapped_region *mr, struct io_uring_region_desc *reg, unsigned long mmap_offset) { @@ -230,8 +230,8 @@ int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, return -EOVERFLOW; nr_pages = reg->size >> PAGE_SHIFT; - if (ctx->user) { - ret = __io_account_mem(ctx->user, nr_pages); + if (user) { + ret = __io_account_mem(user, nr_pages); if (ret) return ret; } @@ -240,7 +240,7 @@ int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, if (reg->flags & IORING_MEM_REGION_TYPE_USER) ret = io_region_pin_pages(mr, reg); else - ret = io_region_allocate_pages(mr, reg, mmap_offset, ctx->user); + ret = io_region_allocate_pages(mr, reg, mmap_offset, user); if (ret) goto out_free; @@ -249,7 +249,7 @@ int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, goto out_free; return 0; out_free: - io_free_region(ctx->user, mr); + io_free_region(user, mr); return ret; } diff --git a/io_uring/memmap.h b/io_uring/memmap.h index f4cfbb6b9a1f..0714bb5a9616 100644 --- a/io_uring/memmap.h +++ b/io_uring/memmap.h @@ -18,7 +18,7 @@ unsigned long io_uring_get_unmapped_area(struct file *file, unsigned long addr, int io_uring_mmap(struct file *file, struct vm_area_struct *vma); void io_free_region(struct user_struct *user, struct io_mapped_region *mr); -int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, +int io_create_region(struct user_struct *user, struct io_mapped_region *mr, struct io_uring_region_desc *reg, unsigned long mmap_offset); diff --git a/io_uring/register.c b/io_uring/register.c index 02bc103bcc9d..d11a53d68431 100644 --- a/io_uring/register.c +++ b/io_uring/register.c @@ -480,8 +480,9 @@ struct io_ring_ctx_rings { static void io_register_free_rings(struct io_ring_ctx *ctx, struct io_ring_ctx_rings *r) { - io_free_region(ctx->user, &r->sq_region); - io_free_region(ctx->user, &r->ring_region); + /* ring memory is not charged to RLIMIT_MEMLOCK, hence no user */ + io_free_region(NULL, &r->sq_region); + io_free_region(NULL, &r->ring_region); } #define swap_old(ctx, o, n, field) \ @@ -529,7 +530,7 @@ static int io_register_resize_rings(struct io_ring_ctx *ctx, void __user *arg) rd.user_addr = p->cq_off.user_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx, &n.ring_region, &rd, IORING_OFF_CQ_RING); + ret = io_create_region(NULL, &n.ring_region, &rd, IORING_OFF_CQ_RING); if (ret) return ret; @@ -559,7 +560,7 @@ static int io_register_resize_rings(struct io_ring_ctx *ctx, void __user *arg) rd.user_addr = p->sq_off.user_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx, &n.sq_region, &rd, IORING_OFF_SQES); + ret = io_create_region(NULL, &n.sq_region, &rd, IORING_OFF_SQES); if (ret) { io_register_free_rings(ctx, &n); return ret; @@ -730,7 +731,8 @@ static int io_register_mem_region(struct io_ring_ctx *ctx, void __user *uarg) !(ctx->flags & IORING_SETUP_R_DISABLED)) return -EINVAL; - ret = io_create_region(ctx, ®ion, &rd, IORING_MAP_OFF_PARAM_REGION); + ret = io_create_region(ctx->user, ®ion, &rd, + IORING_MAP_OFF_PARAM_REGION); if (ret) return ret; if (copy_to_user(rd_uptr, &rd, sizeof(rd))) { diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index beb35077f3ce..939885985333 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -432,7 +432,7 @@ static int io_allocate_rbuf_ring(struct io_ring_ctx *ctx, mmap_offset = IORING_MAP_OFF_ZCRX_REGION; mmap_offset += (u64)id << IORING_OFF_ZCRX_SHIFT; - ret = io_create_region(ctx, &ifq->rq_region, rd, mmap_offset); + ret = io_create_region(ctx->user, &ifq->rq_region, rd, mmap_offset); if (ret < 0) return ret; From cde56daf4b93f474dc256dbcc5205035223ae6f5 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 8 Oct 2026 13:07:38 -0600 Subject: [PATCH 6/9] io_uring/kbuf: don't charge provided buffer rings to RLIMIT_MEMLOCK Just like with the SQ and CQ rings, the provided buffer rings were also not charged against the memlock limit prior to the below mentioned commit. Restore that behavior. Fixes: ef62de3c4ad5 ("io_uring/kbuf: use region api for pbuf rings") Reported-by: Hengyu Liang Cc: stable@vger.kernel.org Tested-by: Hengyu Liang Signed-off-by: Jens Axboe --- io_uring/kbuf.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/io_uring/kbuf.c b/io_uring/kbuf.c index 7c59ab9cfc8b..6e2facf34bfa 100644 --- a/io_uring/kbuf.c +++ b/io_uring/kbuf.c @@ -459,7 +459,7 @@ static int io_remove_buffers_legacy(struct io_ring_ctx *ctx, static void io_put_bl(struct io_ring_ctx *ctx, struct io_buffer_list *bl) { if (bl->flags & IOBL_BUF_RING) - io_free_region(ctx->user, &bl->region); + io_free_region(NULL, &bl->region); else io_remove_buffers_legacy(ctx, bl, -1U); @@ -676,7 +676,7 @@ int io_register_pbuf_ring(struct io_ring_ctx *ctx, void __user *arg) rd.user_addr = reg.ring_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx->user, &bl->region, &rd, mmap_offset); + ret = io_create_region(NULL, &bl->region, &rd, mmap_offset); if (ret) goto fail; br = io_region_get_ptr(&bl->region); @@ -709,7 +709,7 @@ int io_register_pbuf_ring(struct io_ring_ctx *ctx, void __user *arg) if (!ret) return 0; fail: - io_free_region(ctx->user, &bl->region); + io_free_region(NULL, &bl->region); kfree(bl); return ret; } From 6da8f402e362b2f546e2efb7167fc9597aef6faf Mon Sep 17 00:00:00 2001 From: Eugene Gvozdetsky Date: Fri, 9 Oct 2026 01:08:49 +0200 Subject: [PATCH 7/9] io_uring/register: don't exempt disabled rings from task restrictions A ring created with IORING_SETUP_R_DISABLED is exempt from its register restrictions until it is enabled. This lets the creator register the restrictions and then enable the ring with IORING_REGISTER_ENABLE_RINGS, whether or not that opcode is in the allowed set. Since commit ed82f35b926b ("io_uring: allow registration of per-task restrictions"), a ring also inherits restrictions from the task that creates it, and the same exemption applies to those. A task whose register opcodes are restricted can therefore create a disabled ring, issue any register opcode on it, and then enable it. Mark rings whose restrictions were inherited from the task, and keep checking register opcodes for them while the ring is disabled. Enabling such a ring now requires IORING_REGISTER_ENABLE_RINGS to be in the task's allowed set. Per-ring restrictions registered on a disabled ring work as before. Fixes: ed82f35b926b ("io_uring: allow registration of per-task restrictions") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Eugene Gvozdetsky Link: https://patch.msgid.link/20261009-iouring-task-restrict-fix-v1-1-a49daf55a12c@gmail.com [axboe: amend with inheritance flag fixup] Signed-off-by: Jens Axboe --- include/linux/io_uring_types.h | 2 ++ io_uring/io_uring.c | 3 ++- io_uring/register.c | 9 ++++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/include/linux/io_uring_types.h b/include/linux/io_uring_types.h index 6b9e9c669bb3..f9aab8d3f861 100644 --- a/include/linux/io_uring_types.h +++ b/include/linux/io_uring_types.h @@ -317,6 +317,8 @@ enum { IO_RING_F_DRAIN_DISABLED = BIT(10), IO_RING_F_COMPAT = BIT(11), IO_RING_F_IOWQ_LIMITS_SET = BIT(12), + /* restrictions were inherited from the task that created the ring */ + IO_RING_F_RESTRICT_INHERITED = BIT(13), }; struct iou_ctx {}; diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index 79c40e0abf17..c994157173a2 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -2962,7 +2962,8 @@ static void io_ctx_restriction_clone(struct io_ring_ctx *ctx, if (dst->op_registered) ctx->int_flags |= IO_RING_F_OP_RESTRICTED; if (dst->reg_registered) - ctx->int_flags |= IO_RING_F_REG_RESTRICTED; + ctx->int_flags |= IO_RING_F_REG_RESTRICTED | + IO_RING_F_RESTRICT_INHERITED; } static __cold int io_uring_create(struct io_ctx_config *config) diff --git a/io_uring/register.c b/io_uring/register.c index d11a53d68431..dab3eb4258df 100644 --- a/io_uring/register.c +++ b/io_uring/register.c @@ -766,7 +766,14 @@ static int __io_uring_register(struct io_ring_ctx *ctx, unsigned opcode, if (ctx->submitter_task && ctx->submitter_task != current) return -EEXIST; - if ((ctx->int_flags & IO_RING_F_REG_RESTRICTED) && !(ctx->flags & IORING_SETUP_R_DISABLED)) { + /* + * A disabled ring is exempt from its own register restrictions, so + * that the creator can set them up and then enable the ring. That + * does not apply to restrictions inherited from the task. + */ + if ((ctx->int_flags & IO_RING_F_REG_RESTRICTED) && + (!(ctx->flags & IORING_SETUP_R_DISABLED) || + (ctx->int_flags & IO_RING_F_RESTRICT_INHERITED))) { opcode = array_index_nospec(opcode, IORING_REGISTER_LAST); if (!test_bit(opcode, ctx->restrictions.register_op)) return -EACCES; From aa5d5f8e502318e783545ba5d04bb40130b83e20 Mon Sep 17 00:00:00 2001 From: Natasha Klaus Date: Fri, 9 Oct 2026 16:38:07 +0300 Subject: [PATCH 8/9] io_uring/net: populate CONNECT filter fields for 24-byte sockaddr_in6 io_connect_bpf_populate() fills connect.port and connect.v6_addr for AF_INET6 only when addr_len >= sizeof(struct sockaddr_in6), i.e. 28 bytes. The IPv6 connect paths accept SIN6_LEN_RFC2133 (24 bytes), which already holds sin6_port and sin6_addr. io_connect() passes addr_len to the socket layer unchanged. At addr_len 24 the filter sees family AF_INET6 with port and address zero, while the kernel connects to the real destination. A filter that denies an IPv6 port or address is bypassed. Denying connect.port == P rejects [::1]:P with -EACCES at addr_len 28 but connects at addr_len 24. Use SIN6_LEN_RFC2133 as the threshold. sin6_scope_id stays zero when absent. The threshold mismatch was found by an AI coding assistant (Claude Code) comparing this hook against the net/ipv6 length checks on my instructions; it also drafted the reproducer and this patch. I verified the bypass and the fix in a VM on 7.3-rc6 and that 7.2 has the same code. Fixes: 899bea8248ce ("io_uring/net: allow filtering on IORING_OP_CONNECT") Cc: stable@vger.kernel.org # 7.2 Signed-off-by: Natasha Klaus Link: https://patch.msgid.link/20261009133807.730724-1-natalie.klaus@runtimeverification.com Signed-off-by: Jens Axboe --- io_uring/net.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/io_uring/net.c b/io_uring/net.c index 050ed274170a..f5f4f3ce6ef3 100644 --- a/io_uring/net.c +++ b/io_uring/net.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -1741,7 +1742,8 @@ void io_connect_bpf_populate(struct io_uring_bpf_ctx *bctx, struct io_kiocb *req case AF_INET6: { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ss; - if (conn->addr_len < (int)sizeof(*sin6)) + /* The IPv6 stack accepts SIN6_LEN_RFC2133; port and addr are in it. */ + if (conn->addr_len < SIN6_LEN_RFC2133) break; bctx->connect.port = sin6->sin6_port; memcpy(bctx->connect.v6_addr, &sin6->sin6_addr, From fddcf4c5321b3ea5f72166c3fd36a9d1c34f80c7 Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft Security FORGE Labs)" Date: Thu, 8 Oct 2026 15:16:02 -0400 Subject: [PATCH 9/9] io_uring/cancel: don't spin on local work the task can't run io_uring_cancel_generic() refuses to sleep while any ring on the exiting task's tctx has local work pending. It assumes the task is that ring's submitter and that the next io_uring_try_cancel_requests() pass will run the work. This does not hold for a ring that was still IORING_SETUP_R_DISABLED when the task got its tctx node from io_uring_create() or io_ringfd_register(). The submitter is set later by IORING_REGISTER_ENABLE_RINGS, possibly from another task, and only the submitter may run local work on an IORING_SETUP_DEFER_TASKRUN ring. To the exiting task the work stays pending forever, the loop never reaches schedule(), and the task trips the WARN_ON_ONCE() once and then spins in do_exit(). An unprivileged user can set this up at will by creating a disabled ring, having a child enable it and stop, posting to the ring with IORING_OP_MSG_RING and exiting with a request in flight that only the child can complete. The exiting thread burns a CPU until the child runs or dies, and with panic_on_warn set the WARN panics the kernel. Fix by skipping the sleep only when io_allowed_defer_tw_run() allows the task to run the work. This is reasonable since work it cannot run cannot complete any of its requests either, so nothing is lost by sleeping. Drop the WARN_ON_ONCE() since handing a disabled ring to another thread is a supported scenario, not a bug state. Fixes: 360cd42c4e95 ("io_uring: optimise io_req_local_work_add") Reported-by: AutonomousCodeSecurity@microsoft.com Signed-off-by: Cen Zhang (Microsoft Security FORGE Labs) Link: https://patch.msgid.link/20261008191602.45101-1-cenzhang@linux.microsoft.com Signed-off-by: Jens Axboe --- io_uring/cancel.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/io_uring/cancel.c b/io_uring/cancel.c index 7d7820eab878..4f706aff14fc 100644 --- a/io_uring/cancel.c +++ b/io_uring/cancel.c @@ -634,12 +634,11 @@ __cold void io_uring_cancel_generic(bool cancel_all, struct io_sq_data *sqd) prepare_to_wait(&tctx->wait, &wait, TASK_INTERRUPTIBLE); io_run_task_work(); io_uring_drop_tctx_refs(current); + /* only skip the sleep for local work this task may run */ xa_for_each(&tctx->xa, index, node) { - if (io_local_work_pending(node->ctx)) { - WARN_ON_ONCE(node->ctx->submitter_task && - node->ctx->submitter_task != current); + if (io_local_work_pending(node->ctx) && + io_allowed_defer_tw_run(node->ctx)) goto end_wait; - } } /* * If we've seen completions, retry without waiting. This