public inbox for io-uring@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH AUTOSEL 7.0-6.1] io_uring/cancel: validate opcode for IORING_ASYNC_CANCEL_OP
       [not found] <20260420132314.1023554-1-sashal@kernel.org>
@ 2026-04-20 13:22 ` Sasha Levin
  0 siblings, 0 replies; only message in thread
From: Sasha Levin @ 2026-04-20 13:22 UTC (permalink / raw)
  To: patches, stable
  Cc: Amir Mohammad Jahangirzad, Jens Axboe, Sasha Levin, io-uring,
	linux-kernel

From: Amir Mohammad Jahangirzad <a.jahangirzad@gmail.com>

[ Upstream commit 85a58309c0d5b5f5a4b65658312ceaf2c34c9bbf ]

io_async_cancel_prep() reads the opcode selector from sqe->len and
stores it in cancel->opcode, which is an 8-bit field. Since sqe->len
is a 32-bit value, values larger than U8_MAX are implicitly truncated.

This can cause unintended opcode matches when the truncated value
corresponds to a valid io_uring opcode. For example, submitting a value
such as 0x10b will be truncated to 0x0b (IORING_OP_TIMEOUT), allowing a
cancel request to match operations it did not intend to target.
Validate the opcode value before assigning it to the 8-bit field and
reject values outside the valid io_uring opcode range.

Signed-off-by: Amir Mohammad Jahangirzad <a.jahangirzad@gmail.com>
Link: https://patch.msgid.link/20260331232113.615972-1-a.jahangirzad@gmail.com
Signed-off-by: Jens Axboe <axboe@kernel.dk>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

Now I have all the information needed for a complete analysis. Let me
compile the findings.

---

## PHASE 1: COMMIT MESSAGE FORENSICS

### Step 1.1: Subject Line
- **Subsystem**: io_uring/cancel
- **Action verb**: "validate" — this implies adding a missing validation
  check, strongly indicating a bug fix
- **Summary**: Validates the opcode value before storing it in a
  narrower type field to prevent silent truncation

### Step 1.2: Tags
- **Signed-off-by**: Amir Mohammad Jahangirzad (author), Jens Axboe
  (io_uring maintainer accepted it)
- **Link**: `https://patch.msgid.link/20260331232113.615972-1-
  a.jahangirzad@gmail.com`
- No Fixes: tag, no Reported-by, no Cc: stable — all expected for
  autosel review

### Step 1.3: Commit Body Analysis
The commit body clearly explains:
- **Bug mechanism**: `sqe->len` is `__u32` (32-bit), `cancel->opcode` is
  `u8` (8-bit). Without validation, values >255 are silently truncated.
- **Symptom**: Passing 0x10b is truncated to 0x0b (IORING_OP_TIMEOUT),
  causing a cancel request to match timeout operations the user never
  intended to cancel.
- **Root cause**: Missing input validation before narrowing type
  conversion.

### Step 1.4: Hidden Bug Fix Detection
This is an explicit input validation bug fix. The word "validate" makes
it clear, and the commit directly prevents incorrect behavior caused by
integer truncation.

---

## PHASE 2: DIFF ANALYSIS

### Step 2.1: Inventory
- **Files changed**: 1 (`io_uring/cancel.c`)
- **Lines**: +7/-1 (net +6 lines, but only 4 meaningful lines added)
- **Function modified**: `io_async_cancel_prep()`
- **Scope**: Single-file surgical fix in a single function

### Step 2.2: Code Flow Change
Before: `cancel->opcode = READ_ONCE(sqe->len);` — directly assigns
32-bit value to 8-bit field, implicit truncation.

After:
```c
u32 op;
op = READ_ONCE(sqe->len);
if (op >= IORING_OP_LAST)
    return -EINVAL;
cancel->opcode = op;
```
Now validates the value is within the valid opcode range before
assigning.

### Step 2.3: Bug Mechanism
This is a **type/correctness bug** — narrowing conversion without bounds
checking. Category: input validation / logic correctness fix.

### Step 2.4: Fix Quality
- **Obviously correct**: Yes — validates `op >= IORING_OP_LAST` before
  narrowing to u8
- **Minimal/surgical**: Yes — 4 effective lines, single function
- **Regression risk**: Extremely low — the only new behavior is
  rejecting previously-accepted invalid values (which would have caused
  incorrect matching). Existing valid uses are unaffected since all
  valid opcodes are < IORING_OP_LAST and < 256.

---

## PHASE 3: GIT HISTORY INVESTIGATION

### Step 3.1: Blame
The buggy line `cancel->opcode = READ_ONCE(sqe->len);` was introduced in
commit `d7b8b079a8f6bc` by Jens Axboe (2023-06-23), "io_uring/cancel:
support opcode based lookup and cancelation".

### Step 3.2: Fixes Target
No Fixes: tag, but the bug was clearly introduced by `d7b8b079a8f6bc`.
That commit added the `IORING_ASYNC_CANCEL_OP` feature and the opcode
field but neglected to validate the input value.

`d7b8b079a8f6bc` was first released in v6.6-rc1 (verified: not in v6.5,
present in v6.6).

### Step 3.3: Related Changes
Between the buggy commit and HEAD, `io_uring/cancel.c` had ~20 non-merge
changes, mostly structural reorganization (moving code to cancel.c from
io_uring.c). None fix this specific truncation issue.

### Step 3.4: Author
The author (Amir Mohammad Jahangirzad) is not the subsystem maintainer
but has submitted other kernel fixes. The patch was accepted and signed
by Jens Axboe, the io_uring subsystem maintainer.

### Step 3.5: Dependencies
None. This is a standalone, self-contained fix. The `IORING_OP_LAST`
sentinel has existed since the io_uring opdef array was created and is
present in all relevant stable trees.

---

## PHASE 4: MAILING LIST RESEARCH

### Step 4.1: Original Patch
Lore.kernel.org is behind an anti-bot wall; b4 dig could not find the
commit by message-id (commit too recent for the local index). The Link
tag in the commit confirms the patch was submitted and reviewed on the
mailing list.

### Step 4.2: Reviewer
Jens Axboe (io_uring maintainer) signed off and merged the patch, which
is strong validation.

### Step 4.3–4.5
No bug report references found. No explicit stable nomination found. The
bug was apparently discovered by code inspection rather than a runtime
report.

---

## PHASE 5: CODE SEMANTIC ANALYSIS

### Step 5.1: Modified Function
`io_async_cancel_prep()` — the SQE preparation function for
`IORING_OP_ASYNC_CANCEL`.

### Step 5.2: Callers
`io_async_cancel_prep` is called from the opdef table
(`io_uring/opdef.c` line 196) as the `.prep` handler for
`IORING_OP_ASYNC_CANCEL`. This is triggered every time a user submits an
async cancel request through the io_uring interface.

### Step 5.3: Impact Path
The opcode stored here flows into `io_cancel_data.opcode`, which is
compared against `req->opcode` in `io_cancel_req_match()`. A truncated
opcode would match the wrong operations.

### Step 5.4: Reachability
This is directly reachable from userspace via `io_uring_enter()` → SQE
processing → `io_async_cancel_prep()`. Any unprivileged user with access
to io_uring can trigger this.

### Step 5.5: io_sync_cancel path
The `io_sync_cancel` path reads from `struct io_uring_sync_cancel_reg`
where `opcode` is already `__u8`, so truncation cannot occur there.
However, that path also lacks a `>= IORING_OP_LAST` check, which is a
separate (less critical) issue since values 0-255 that exceed
IORING_OP_LAST would simply not match any operation.

---

## PHASE 6: STABLE TREE ANALYSIS

### Step 6.1: Affected Stable Trees
The buggy code (`d7b8b079a8f6bc`) was introduced in v6.6-rc1 (confirmed:
present in v6.6, not in v6.5). All stable trees from 6.6.y onward
contain the bug: 6.6.y, 6.12.y.

### Step 6.2: Backport Complications
The fix modifies a simple code block that has not changed since the
original introduction. It should apply cleanly to any stable tree that
has the IORING_ASYNC_CANCEL_OP feature (6.6+). `IORING_OP_LAST` exists
in all these trees.

### Step 6.3: No prior fix
No other fix for this same issue was found in the commit history.

---

## PHASE 7: SUBSYSTEM CONTEXT

### Step 7.1: Subsystem
**io_uring** — a high-performance async I/O interface. Criticality:
**IMPORTANT**. Widely used in modern Linux applications, database
systems, and high-performance servers.

### Step 7.2: Activity
io_uring is very actively developed with frequent changes. Jens Axboe is
the sole maintainer.

---

## PHASE 8: IMPACT AND RISK ASSESSMENT

### Step 8.1: Affected Users
All users of io_uring who use the `IORING_ASYNC_CANCEL_OP` feature
(cancel by opcode). This includes both applications and libraries like
liburing.

### Step 8.2: Trigger
- A user submits an `IORING_OP_ASYNC_CANCEL` SQE with
  `IORING_ASYNC_CANCEL_OP` flag and `sqe->len` value > 255
- The value silently truncates, potentially matching a valid opcode
- Example: 0x10b → 0x0b (IORING_OP_TIMEOUT)
- **Unprivileged users can trigger this**

### Step 8.3: Severity
- **Failure mode**: Wrong operations get cancelled unexpectedly
- **Impact**: Application-level data loss or incorrect behavior (e.g.,
  timeouts cancelled that shouldn't be, leading to stuck operations or
  missed deadlines)
- **Severity**: MEDIUM — not a kernel crash, but causes incorrect
  kernel-userspace contract behavior

### Step 8.4: Risk-Benefit
- **Benefit**: Prevents incorrect operation cancellation due to integer
  truncation. Clear correctness fix.
- **Risk**: Very low — the only behavioral change is rejecting invalid
  opcodes (>= IORING_OP_LAST) that previously caused silent truncation.
  Valid applications are completely unaffected.
- **Ratio**: High benefit / very low risk

---

## PHASE 9: FINAL SYNTHESIS

### Step 9.1: Evidence
**FOR backporting:**
- Fixes a real input validation bug (u32→u8 truncation without bounds
  check)
- Can cause incorrect operation matching (wrong cancellations)
- Reachable from unprivileged userspace
- Fix is minimal (4 effective lines), obviously correct, zero regression
  risk
- Accepted by io_uring maintainer Jens Axboe
- Bug present in active stable trees (6.6.y, 6.12.y)
- Standalone fix, no dependencies

**AGAINST backporting:**
- Not a crash, security vulnerability, or data corruption at the kernel
  level
- Requires passing an invalid opcode value (>255) to trigger
- No user reports of this bug being hit in practice

### Step 9.2: Stable Rules Checklist
1. **Obviously correct and tested?** YES — trivial bounds check
2. **Fixes a real bug?** YES — integer truncation causes wrong operation
   matching
3. **Important issue?** MEDIUM — incorrect io_uring behavior, not a
   crash
4. **Small and contained?** YES — 4 lines in one function
5. **No new features/APIs?** CORRECT — only adds validation
6. **Can apply to stable?** YES — clean apply expected for 6.6+

### Step 9.3: Exception Categories
Not an exception category; standard bug fix.

### Step 9.4: Decision
This is a clear input validation bug with a trivial, risk-free fix.
While not a crash-level severity, it causes the kernel to violate the
io_uring API contract — an opcode value of 0x10b should either be
rejected or never match opcode 0x0b. Silently doing the wrong thing is a
real bug. The fix is obviously correct, tiny, and cannot regress
anything.

---

## Verification

- [Phase 1] Parsed subject: "validate opcode for IORING_ASYNC_CANCEL_OP"
  — clear input validation fix
- [Phase 1] Tags: Signed-off-by Jens Axboe (maintainer), Link to lore
- [Phase 2] Diff: +7/-1 in io_async_cancel_prep(), adds u32 temp + range
  check before u8 assignment
- [Phase 2] Verified sqe->len is `__u32` (include/uapi/linux/io_uring.h
  line 51), cancel->opcode is `u8` (cancel.c line 29)
- [Phase 3] git blame: buggy line introduced by d7b8b079a8f6bc (Jens
  Axboe, 2023-06-23)
- [Phase 3] git merge-base: d7b8b079a8f6bc is in v6.6 but NOT in v6.5
  (confirmed with --is-ancestor)
- [Phase 3] git describe: first in v6.6-rc1~152^2~40
- [Phase 4] b4 dig on d7b8b079a8f6bc: original series at
  lore.kernel.org/all/20230623164804.610910-8-axboe@kernel.dk/
- [Phase 4] Lore anti-bot blocked direct web access
- [Phase 5] io_async_cancel_prep called from opdef.c line 196, reachable
  from userspace io_uring_enter
- [Phase 5] Verified io_sync_cancel uses __u8 opcode in struct
  io_uring_sync_cancel_reg — no truncation there
- [Phase 5] Verified IORING_OP_LAST exists in
  include/uapi/linux/io_uring.h line 321
- [Phase 6] Buggy code present in 6.6.y+ stable trees (verified)
- [Phase 6] No conflicting changes found — clean apply expected
- [Phase 8] Unprivileged userspace trigger confirmed — io_uring
  accessible without privileges
- UNVERIFIED: Could not access lore discussion for the specific fix
  commit due to anti-bot protection

**YES**

 io_uring/cancel.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/io_uring/cancel.c b/io_uring/cancel.c
index 65e04063e343b..5e5eb9cfc7cd6 100644
--- a/io_uring/cancel.c
+++ b/io_uring/cancel.c
@@ -156,9 +156,16 @@ int io_async_cancel_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
 		cancel->fd = READ_ONCE(sqe->fd);
 	}
 	if (cancel->flags & IORING_ASYNC_CANCEL_OP) {
+		u32 op;
+
 		if (cancel->flags & IORING_ASYNC_CANCEL_ANY)
 			return -EINVAL;
-		cancel->opcode = READ_ONCE(sqe->len);
+
+		op = READ_ONCE(sqe->len);
+		if (op >= IORING_OP_LAST)
+			return -EINVAL;
+
+		cancel->opcode = op;
 	}
 
 	return 0;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-04-20 13:33 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <20260420132314.1023554-1-sashal@kernel.org>
2026-04-20 13:22 ` [PATCH AUTOSEL 7.0-6.1] io_uring/cancel: validate opcode for IORING_ASYNC_CANCEL_OP Sasha Levin

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