All of lore.kernel.org
 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 related	[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 related	[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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.