From 50b617ba788e5c27dfc6efc90556cdcc9509028d Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Sat, 19 Sep 2026 11:25:23 +0000 Subject: [PATCH 1/8] io_uring/bpf_filter: mark source as COW when cloning filters io_bpf_filter_clone() shares the filter table between source and destination and marks the destination as needing a copy before it can be modified, but leaves the source as is. Registering another filter on the source then modifies the shared table in place, and the change shows up in everything that cloned it. Mark the source as COW as well. Fixes: ed82f35b926b ("io_uring: allow registration of per-task restrictions") Assisted-by: LLM Signed-off-by: Hui Peng Link: https://patch.msgid.link/20260919112523.3872581-1-benquike@gmail.com [axboe: edited patch and commit message] Signed-off-by: Jens Axboe --- io_uring/bpf_filter.c | 1 + 1 file changed, 1 insertion(+) diff --git a/io_uring/bpf_filter.c b/io_uring/bpf_filter.c index c0037632b7af..4a21511c4811 100644 --- a/io_uring/bpf_filter.c +++ b/io_uring/bpf_filter.c @@ -253,6 +253,7 @@ void io_bpf_filter_clone(struct io_restriction *dst, struct io_restriction *src) * If the src filter is going away, just ignore it. */ if (refcount_inc_not_zero(&src->bpf_filters->refs)) { + src->bpf_filters_cow = true; dst->bpf_filters = src->bpf_filters; dst->bpf_filters_cow = true; } From 2107751744287996a9bbcf40b5055a5b7fe30b5c Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Sat, 19 Sep 2026 20:35:16 +0000 Subject: [PATCH 2/8] io_uring: only post the dummy skip CQE on CQE_MIXED rings io_cqe_cache_refill() posts a dummy skip CQE when a 32b CQE would straddle the end of the ring, so the real one can start at offset 0. That only makes sense on CQE_MIXED rings, where slots are 16b and a 32b CQE needs two of them back to back. On a CQE32 ring every slot is already 32b, but io_fill_cqe_aux32() still passes cqe32 == true, and the dummy then gets written at cqes[off] rather than cqes[off << 1], inside another CQE. Limit the dummy to CQE_MIXED rings. Fixes: e26dca67fde1 ("io_uring: add support for IORING_SETUP_CQE_MIXED") Assisted-by: LLM Signed-off-by: Hui Peng Link: https://patch.msgid.link/20260919203516.2581409-1-benquike@gmail.com [axboe: split from Hui's original patch, edited commit message] Signed-off-by: Jens Axboe --- io_uring/io_uring.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index 76f049e29aa2..88fed477e2b0 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -733,7 +733,8 @@ bool io_cqe_cache_refill(struct io_ring_ctx *ctx, bool overflow, bool cqe32) * Post dummy CQE if a 32b CQE is needed and there's only room for a * 16b CQE before the ring wraps. */ - if (cqe32 && off + 1 == ctx->cq_entries) { + if (cqe32 && (ctx->flags & IORING_SETUP_CQE_MIXED) && + off + 1 == ctx->cq_entries) { if (!io_fill_nop_cqe(ctx, off)) return false; off = 0; From ab5853c068b1d654d7c4a535417ca3eb3e67403b Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Sat, 19 Sep 2026 20:35:18 +0000 Subject: [PATCH 3/8] io_uring: fix free entry check for 32b CQEs on CQE32 rings On CQE32 rings, len is in units of 32b CQEs when io_cqe_cache_refill() checks for free entries, so requiring cqe32 + 1 of them means insisting on 2 free CQEs where 1 is enough, and a ring with a single free entry needlessly goes to overflow. Check for a free CQE directly in that case, and keep the cqe32 + 1 check for CQE_MIXED where the units are 16b. Fixes: e26dca67fde1 ("io_uring: add support for IORING_SETUP_CQE_MIXED") Assisted-by: LLM Signed-off-by: Hui Peng Link: https://patch.msgid.link/20260919203516.2581409-1-benquike@gmail.com [axboe: split from Hui's original patch, edited commit message] Signed-off-by: Jens Axboe --- io_uring/io_uring.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index 88fed477e2b0..4847a8f8fac8 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -743,12 +743,13 @@ bool io_cqe_cache_refill(struct io_ring_ctx *ctx, bool overflow, bool cqe32) free = ctx->cq_entries - io_cqring_queued(ctx); /* we need a contiguous range, limit based on the current array offset */ len = min(free, ctx->cq_entries - off); - if (len < (cqe32 + 1)) - return false; - if (ctx->flags & IORING_SETUP_CQE32) { + if (!len) + return false; off <<= 1; len <<= 1; + } else if (len < (cqe32 + 1)) { + return false; } ctx->cqe_cached = &rings->cqes[off]; From f11a16c8200189a3752b5233092f88503969166d Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Sat, 19 Sep 2026 20:35:19 +0000 Subject: [PATCH 4/8] io_uring: zero big_cqe for aux CQEs on CQE32 rings io_fill_cqe_aux() only clears big_cqe if cqe32 is set, which is only the case for CQE_MIXED. On a CQE32 ring every CQE is 32b, and the extra 16b are left holding whatever was in that slot before. Clear it for CQE32 rings as well, like it was before CQE_MIXED support was added. Fixes: e26dca67fde1 ("io_uring: add support for IORING_SETUP_CQE_MIXED") Assisted-by: LLM Signed-off-by: Hui Peng Link: https://patch.msgid.link/20260919203516.2581409-1-benquike@gmail.com [axboe: split from Hui's original patch, edited commit message] Signed-off-by: Jens Axboe --- io_uring/io_uring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index 4847a8f8fac8..2cb9b58d04dd 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -783,7 +783,7 @@ static bool io_fill_cqe_aux(struct io_ring_ctx *ctx, u64 user_data, s32 res, WRITE_ONCE(cqe->res, res); WRITE_ONCE(cqe->flags, cflags); - if (cqe32) { + if (cqe32 || (ctx->flags & IORING_SETUP_CQE32)) { WRITE_ONCE(cqe->big_cqe[0], 0); WRITE_ONCE(cqe->big_cqe[1], 0); } From a3bdf68feecc57af5c11fb599f860ac9790ffad9 Mon Sep 17 00:00:00 2001 From: Yao Kai Date: Wed, 23 Sep 2026 09:43:10 +0800 Subject: [PATCH 5/8] 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 6/8] 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 7/8] 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 8/8] 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;