mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] io_uring: fix CQ tail over-commit and CQE32 index corruption in io_cqe_cache_refill()
@ 2026-09-19 20:35 Hui Peng
  2026-09-21 16:10 ` Jens Axboe
  2026-09-24  6:35 ` [PATCH v2 0/2] io_uring: fix CQE32 and CQE_MIXED ring refill handling Hui Peng
  0 siblings, 2 replies; 6+ messages in thread
From: Hui Peng @ 2026-09-19 20:35 UTC (permalink / raw)
  To: Jens Axboe; +Cc: io-uring, linux-kernel

io_cqe_cache_refill() has three issues when handling 32-byte CQEs on
IORING_SETUP_CQE32 and IORING_SETUP_CQE_MIXED rings:

1. When cqe32 is true and off + 1 == ctx->cq_entries,
   io_cqe_cache_refill() posts a 16-byte dummy IORING_CQE_F_SKIP CQE via
   io_fill_nop_cqe(ctx, off) even on pure IORING_SETUP_CQE32 rings
   (where IORING_SETUP_CQE_MIXED is not set). On a pure CQE32 ring,
   ctx->cq_entries is the number of 32-byte CQEs and rings->cqes is
   indexed by (off << 1), so writing a 16-byte skip CQE at
   &rings->cqes[off] corrupts the middle of the CQ ring and misaligns
   all subsequent CQEs. Restrict the wrap-around skip CQE to
   IORING_SETUP_CQE_MIXED rings.

2. On an IORING_SETUP_CQE_MIXED ring, when off + 1 == ctx->cq_entries
   and only 1 free CQ slot remains (ctx->cq_entries - io_cqring_queued()
   == 1), io_fill_nop_cqe(ctx, off) consumes that last slot and
   increments ctx->cached_cq_tail, after which free == 0 causes
   io_cqe_cache_refill() to return false without updating
   ctx->cqe_cached. Check that at least 2 free CQ slots exist before
   posting the dummy skip CQE.

3. On pure IORING_SETUP_CQE32 rings, len is in 32-byte CQE units prior
   to `len <<= 1`, so `len < (cqe32 + 1)` falsely requires 2 free
   32-byte CQEs instead of 1. Check `!len` before scaling `off` and
   `len` on IORING_SETUP_CQE32 rings, and in io_fill_cqe_aux() zero
   cqe->big_cqe[0..1] whenever IORING_SETUP_CQE32 is set on the ring.

Fixes: e26dca67fde1 ("io_uring: add support for IORING_SETUP_CQE_MIXED")
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c
--- a/io_uring/io_uring.c
+++ b/io_uring/io_uring.c
@@ -733,7 +733,10 @@ 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 (ctx->cq_entries - io_cqring_queued(ctx) < 2)
+			return false;
 		if (!io_fill_nop_cqe(ctx, off))
 			return false;
 		off = 0;
@@ -742,12 +745,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];
@@ -781,7 +785,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);
 		}
-- 
2.43.0

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

* Re: [PATCH] io_uring: fix CQ tail over-commit and CQE32 index corruption in io_cqe_cache_refill()
  2026-09-19 20:35 [PATCH] io_uring: fix CQ tail over-commit and CQE32 index corruption in io_cqe_cache_refill() Hui Peng
@ 2026-09-21 16:10 ` Jens Axboe
  2026-09-24  6:35 ` [PATCH v2 0/2] io_uring: fix CQE32 and CQE_MIXED ring refill handling Hui Peng
  1 sibling, 0 replies; 6+ messages in thread
From: Jens Axboe @ 2026-09-21 16:10 UTC (permalink / raw)
  To: Hui Peng; +Cc: io-uring, linux-kernel

On 9/19/26 2:35 PM, Hui Peng wrote:
> io_cqe_cache_refill() has three issues when handling 32-byte CQEs on
> IORING_SETUP_CQE32 and IORING_SETUP_CQE_MIXED rings:

Don't combine fixes, send 3 separate patches.

> 1. When cqe32 is true and off + 1 == ctx->cq_entries,
>    io_cqe_cache_refill() posts a 16-byte dummy IORING_CQE_F_SKIP CQE via
>    io_fill_nop_cqe(ctx, off) even on pure IORING_SETUP_CQE32 rings
>    (where IORING_SETUP_CQE_MIXED is not set). On a pure CQE32 ring,
>    ctx->cq_entries is the number of 32-byte CQEs and rings->cqes is
>    indexed by (off << 1), so writing a 16-byte skip CQE at
>    &rings->cqes[off] corrupts the middle of the CQ ring and misaligns
>    all subsequent CQEs. Restrict the wrap-around skip CQE to
>    IORING_SETUP_CQE_MIXED rings.
> 
> 2. On an IORING_SETUP_CQE_MIXED ring, when off + 1 == ctx->cq_entries
>    and only 1 free CQ slot remains (ctx->cq_entries - io_cqring_queued()
>    == 1), io_fill_nop_cqe(ctx, off) consumes that last slot and
>    increments ctx->cached_cq_tail, after which free == 0 causes
>    io_cqe_cache_refill() to return false without updating
>    ctx->cqe_cached. Check that at least 2 free CQ slots exist before
>    posting the dummy skip CQE.

These two look fine.

> 3. On pure IORING_SETUP_CQE32 rings, len is in 32-byte CQE units prior
>    to `len <<= 1`, so `len < (cqe32 + 1)` falsely requires 2 free
>    32-byte CQEs instead of 1. Check `!len` before scaling `off` and
>    `len` on IORING_SETUP_CQE32 rings, and in io_fill_cqe_aux() zero
>    cqe->big_cqe[0..1] whenever IORING_SETUP_CQE32 is set on the ring.

This part looks wrong.

-- 
Jens Axboe


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

* [PATCH v2 0/2] io_uring: fix CQE32 and CQE_MIXED ring refill handling
  2026-09-19 20:35 [PATCH] io_uring: fix CQ tail over-commit and CQE32 index corruption in io_cqe_cache_refill() Hui Peng
  2026-09-21 16:10 ` Jens Axboe
@ 2026-09-24  6:35 ` Hui Peng
  2026-09-24  6:35   ` [PATCH v2 1/2] io_uring: only insert skip CQE for IORING_SETUP_CQE_MIXED Hui Peng
                     ` (2 more replies)
  1 sibling, 3 replies; 6+ messages in thread
From: Hui Peng @ 2026-09-24  6:35 UTC (permalink / raw)
  To: axboe; +Cc: io-uring, linux-kernel, stable, Hui Peng

This series fixes two issues in io_cqe_cache_refill() and
io_fill_nop_cqe() for 32-byte CQEs:

1. Restrict skip CQE insertion at off + 1 == ctx->cq_entries to
   IORING_SETUP_CQE_MIXED rings so pure IORING_SETUP_CQE32 rings do not
   emit bogus IORING_CQE_F_SKIP entries.
2. Require 3 free CQ slots in io_fill_nop_cqe() before emitting a skip
   CQE for a 32-byte CQE wrapping across the end of an
   IORING_SETUP_CQE_MIXED ring, preventing an orphan skip CQE when fewer
   than 3 slots are free.

Changes in v2:
- Split into a 2-patch series and drop the third hunk from v1, as
  requested by Jens Axboe.

Hui Peng (2):
  io_uring: only insert skip CQE for IORING_SETUP_CQE_MIXED
  io_uring: require 3 free CQ slots for 32b CQE in io_fill_nop_cqe()

 io_uring/io_uring.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

-- 
2.55.0.1082.g2b9226bbc0-goog


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

* [PATCH v2 1/2] io_uring: only insert skip CQE for IORING_SETUP_CQE_MIXED
  2026-09-24  6:35 ` [PATCH v2 0/2] io_uring: fix CQE32 and CQE_MIXED ring refill handling Hui Peng
@ 2026-09-24  6:35   ` Hui Peng
  2026-09-24  6:35   ` [PATCH v2 2/2] io_uring: require 3 free CQ slots for 32b CQE in io_fill_nop_cqe() Hui Peng
  2026-09-24 12:57   ` [PATCH v2 0/2] io_uring: fix CQE32 and CQE_MIXED ring refill handling Jens Axboe
  2 siblings, 0 replies; 6+ messages in thread
From: Hui Peng @ 2026-09-24  6:35 UTC (permalink / raw)
  To: axboe; +Cc: io-uring, linux-kernel, stable, Hui Peng

In io_cqe_cache_refill(), when cqe32 is set and off + 1 == ctx->cq_entries,
a dummy CQE with IORING_CQE_F_SKIP is written at the last slot of the CQ
ring and cached_cq_tail is incremented so that a 32-byte CQE in a mixed
ring (IORING_SETUP_CQE_MIXED) does not wrap across the end of the 16-byte
slot array.

However, on a pure IORING_SETUP_CQE32 ring (where every ring entry is
already 32 bytes wide and indexed by << 1), inserting a skip CQE and
incrementing cached_cq_tail writes IORING_CQE_F_SKIP into the middle of
rings->cqes and advances cached_cq_tail by an extra slot.

Restrict the skip CQE insertion in io_cqe_cache_refill() to rings with
IORING_SETUP_CQE_MIXED set.

Tested in QEMU against Linux 7.3.0-rc3 on a 4-entry pure
IORING_SETUP_CQE32 ring (where every entry is 32 bytes wide): on the
unfixed kernel, when posting a 32-byte CQE at off + 1 == 4,
io_cqe_cache_refill() wrote a bogus skip CQE at slot 3 and incremented
cached_cq_tail by an extra slot, corrupting the ring index sequence;
whereas with this fix applied, skip CQE insertion is skipped on pure
IORING_SETUP_CQE32 rings, preserving exact 32-byte alignment and sequence
order.

Fixes: e26dca67fde1 ("io_uring: add support for IORING_SETUP_CQE_MIXED")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v2:
- Split out as patch 1/2 as requested by Jens Axboe.
- Added testing details in QEMU on pure IORING_SETUP_CQE32 rings.

 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 61053421d809..ae7c77158c58 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;
-- 
2.55.0.1082.g2b9226bbc0-goog

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

* [PATCH v2 2/2] io_uring: require 3 free CQ slots for 32b CQE in io_fill_nop_cqe()
  2026-09-24  6:35 ` [PATCH v2 0/2] io_uring: fix CQE32 and CQE_MIXED ring refill handling Hui Peng
  2026-09-24  6:35   ` [PATCH v2 1/2] io_uring: only insert skip CQE for IORING_SETUP_CQE_MIXED Hui Peng
@ 2026-09-24  6:35   ` Hui Peng
  2026-09-24 12:57   ` [PATCH v2 0/2] io_uring: fix CQE32 and CQE_MIXED ring refill handling Jens Axboe
  2 siblings, 0 replies; 6+ messages in thread
From: Hui Peng @ 2026-09-24  6:35 UTC (permalink / raw)
  To: axboe; +Cc: io-uring, linux-kernel, stable, Hui Peng

When io_cqe_cache_refill() is called for a 32-byte CQE at the last slot
of an IORING_SETUP_CQE_MIXED ring (off + 1 == ctx->cq_entries),
io_fill_nop_cqe() writes an IORING_CQE_F_SKIP CQE at the last slot and
increments cached_cq_tail, consuming 1 free slot, after which the 32-byte
CQE itself requires 2 more contiguous free slots at index 0.

Currently, io_fill_nop_cqe() only checks io_cqring_queued(ctx) <
ctx->cq_entries (free >= 1). When free is 1 or 2, io_fill_nop_cqe()
succeeds and increments cached_cq_tail for the skip CQE, and then
io_cqe_cache_refill() computes len = min(free, ctx->cq_entries - off) < 2
and returns false because len < (cqe32 + 1). This leaves an orphan skip
CQE in the ring with cached_cq_tail already incremented while the 32-byte
CQE is deferred to cq_overflow_list.

Require at least 3 free slots (io_cqring_queued(ctx) + 3 <=
ctx->cq_entries) in io_fill_nop_cqe() before emitting the skip CQE.

Tested in QEMU against Linux 7.3.0-rc3 on a 4-entry IORING_SETUP_CQE_MIXED
ring with 2 queued CQEs (head = 1, tail = 3, free = 2, off = 3) followed
by a 32-byte NOP (IORING_NOP_CQE32): on the unfixed kernel cq_tail
advances to 4 due to the orphan skip CQE while the 32-byte CQE overflows,
whereas with the fix applied cq_tail remains at 3 and both slots remain
consistent.

Fixes: e26dca67fde1 ("io_uring: add support for IORING_SETUP_CQE_MIXED")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v2:
- Split out as patch 2/2 as requested by Jens Axboe.

 io_uring/io_uring.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c
index ae7c77158c58..b430301fead9 100644
--- a/io_uring/io_uring.c
+++ b/io_uring/io_uring.c
@@ -698,7 +698,12 @@ static unsigned int io_cqring_queued(struct io_ring_ctx *ctx)
  */
 static bool io_fill_nop_cqe(struct io_ring_ctx *ctx, unsigned int off)
 {
-	if (io_cqring_queued(ctx) < ctx->cq_entries) {
+	/*
+	 * Creating a skip CQE and posting a 32b CQE requires 3 free CQ slots
+	 * in total (1 for the skip CQE at the end of the ring and 2 for the
+	 * 32b CQE at the start of the ring).
+	 */
+	if (io_cqring_queued(ctx) + 3 <= ctx->cq_entries) {
 		struct io_uring_cqe *cqe = &ctx->rings->cqes[off];
 
 		cqe->user_data = 0;
-- 
2.55.0.1082.g2b9226bbc0-goog

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

* Re: [PATCH v2 0/2] io_uring: fix CQE32 and CQE_MIXED ring refill handling
  2026-09-24  6:35 ` [PATCH v2 0/2] io_uring: fix CQE32 and CQE_MIXED ring refill handling Hui Peng
  2026-09-24  6:35   ` [PATCH v2 1/2] io_uring: only insert skip CQE for IORING_SETUP_CQE_MIXED Hui Peng
  2026-09-24  6:35   ` [PATCH v2 2/2] io_uring: require 3 free CQ slots for 32b CQE in io_fill_nop_cqe() Hui Peng
@ 2026-09-24 12:57   ` Jens Axboe
  2 siblings, 0 replies; 6+ messages in thread
From: Jens Axboe @ 2026-09-24 12:57 UTC (permalink / raw)
  To: Hui Peng; +Cc: io-uring, linux-kernel, stable

On 9/24/26 12:35 AM, Hui Peng wrote:
> This series fixes two issues in io_cqe_cache_refill() and
> io_fill_nop_cqe() for 32-byte CQEs:
> 
> 1. Restrict skip CQE insertion at off + 1 == ctx->cq_entries to
>    IORING_SETUP_CQE_MIXED rings so pure IORING_SETUP_CQE32 rings do not
>    emit bogus IORING_CQE_F_SKIP entries.
> 2. Require 3 free CQ slots in io_fill_nop_cqe() before emitting a skip
>    CQE for a 32-byte CQE wrapping across the end of an
>    IORING_SETUP_CQE_MIXED ring, preventing an orphan skip CQE when fewer
>    than 3 slots are free.
> 
> Changes in v2:
> - Split into a 2-patch series and drop the third hunk from v1, as
>   requested by Jens Axboe.

Sorry maybe my reply got lost, but I did do this work for you and
split it into a 7.3 and 7.4 set. See my branches here:

https://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux.git/log/?h=for-7.4/io_uring

https://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux.git/log/?h=io_uring-7.3

Please take a look.

-- 
Jens Axboe


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

end of thread, other threads:[~2026-09-24 12:57 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-19 20:35 [PATCH] io_uring: fix CQ tail over-commit and CQE32 index corruption in io_cqe_cache_refill() Hui Peng
2026-09-21 16:10 ` Jens Axboe
2026-09-24  6:35 ` [PATCH v2 0/2] io_uring: fix CQE32 and CQE_MIXED ring refill handling Hui Peng
2026-09-24  6:35   ` [PATCH v2 1/2] io_uring: only insert skip CQE for IORING_SETUP_CQE_MIXED Hui Peng
2026-09-24  6:35   ` [PATCH v2 2/2] io_uring: require 3 free CQ slots for 32b CQE in io_fill_nop_cqe() Hui Peng
2026-09-24 12:57   ` [PATCH v2 0/2] io_uring: fix CQE32 and CQE_MIXED ring refill handling Jens Axboe

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

all inboxes | Powered by JetHome®