syzbot
BUG: unable to handle kernel paging request in blk_mq_complete_request_remote (2)
Status:
moderation: reported on 2026/11/15 18:48
Subsystems:
block
Labels:
prio:high
[Documentation on labels]
Reported-by: syzbot+e3f20f68978d64d72502@syzkaller.appspotmail.com
First crash: 4d01h, last: 4d01h
✨ AI-generated root cause summary:
During an NVMe controller reset failure, nvme_reset_work invokes nvme_dev_disable a second time after an earlier disable call had already cancelled and freed in-flight requests (clearing rq->mq_hctx to NULL in __blk_mq_free_request). Because request cancellation via nvme_cancel_request does not advance the request generation counter (genctr), and nvme_find_rq in nvme_handle_cqe only checks tag bounds and genctr matching without verifying if the request is active (blk_mq_request_started), nvme_find_rq returns an already-completed and freed struct request. When nvme_try_complete_req passes this request to blk_mq_complete_request_remote, dereferencing rq->mq_hctx->nr_ctx (offset 0x18e) results in a NULL pointer dereference crash.
▶
▼
✨ Full AI root cause analysis
Bug Analysis & Technical Explanation
Overview and Manifestation
The crash occurs in blk_mq_complete_request_remote() (block/blk-mq.c:1317):
bool blk_mq_complete_request_remote(struct request *rq)
{
WRITE_ONCE(rq->state, MQ_RQ_COMPLETE);
/*
* For request which hctx has only one ctx mapping,
* or a polled request, always complete locally,
* it's pointless to redirect the completion.
*/
if ((rq->mq_hctx->nr_ctx == 1 &&
rq->mq_ctx->cpu == raw_smp_processor_id()) ||
rq->cmd_flags & REQ_POLLED)
return false;
...
In blk_mq_complete_request_remote(), the expression rq->mq_hctx->nr_ctx accesses nr_ctx at offset 0x18e within struct blk_mq_hw_ctx. At the time of the fault, rq (0xffff0000c8ca9000) is a valid pointer to a struct request, but rq->mq_hctx is NULL. Consequently, KASAN intercepts an access to 0x000000000000018e (KASAN shadow address dfff800000000031), triggering a NULL-pointer dereference kernel Oops (ESR = 0x96000005: level 1 translation fault).
Root Cause Analysis
-
Request Lifecycle and Stale Tag Mapping in blk-mq:
- When a request is queued and started via
blk_mq_start_request() (block/blk-mq.c:1375), it sets rq->state = MQ_RQ_IN_FLIGHT and saves the request into the driver tags array: rq->mq_hctx->tags->rqs[rq->tag] = rq;.
- When a request finishes and is freed via
blk_mq_free_request() (block/blk-mq.c:840) $\to$ __blk_mq_free_request() (block/blk-mq.c:828), rq->state transitions to MQ_RQ_IDLE and rq->mq_hctx is explicitly cleared to NULL (rq->mq_hctx = NULL;).
- However,
tags->rqs[tag] is not cleared upon request completion; it retains the pointer to the static request allocation.
-
NVMe Cancellation vs. Generation Counter (genctr):
- Under normal request completion (
nvme_try_complete_req(), drivers/nvme/host/nvme.h:834), the controller increments rq->genctr (rq->genctr++).
- When an NVMe controller is disabled,
nvme_dev_disable() calls nvme_cancel_tagset(&dev->ctrl) $\to$ blk_mq_tagset_busy_iter() with callback nvme_cancel_request() (drivers/nvme/host/core.c:543).
nvme_cancel_request() sets nvme_req(req)->status = NVME_SC_HOST_ABORTED_CMD and calls blk_mq_complete_request(req) directly. It does not call nvme_try_complete_req() and does not advance rq->genctr. The cancelled requests are completed and freed, leaving rq->mq_hctx = NULL, while rq->genctr remains at the previous generation.
-
Controller Reset Failure and Duplicate nvme_dev_disable():
-
Reaping CQEs with Idle/Freed Requests:
- In
nvme_dev_disable(dev, true), line 3565 calls nvme_reap_pending_cqes(dev):
static void nvme_reap_pending_cqes(struct nvme_dev *dev)
{
int i;
for (i = dev->ctrl.queue_count - 1; i > 0; i--) {
spin_lock(&dev->queues[i].cq_poll_lock);
nvme_poll_cq(&dev->queues[i], NULL);
spin_unlock(&dev->queues[i].cq_poll_lock);
}
}
- If any completion queue entry is detected pending (
nvme_cqe_pending() matching phase), nvme_handle_cqe() is invoked.
nvme_handle_cqe() looks up the request using nvme_find_rq(nvme_queue_tagset(nvmeq), command_id).
- In
nvme_find_rq() (drivers/nvme/host/nvme.h:692-712):
rq = blk_mq_tag_to_rq(tags, tag);
if (unlikely(!rq))
return NULL;
if (unlikely(nvme_genctr_mask(nvme_req(rq)->genctr) != genctr))
return NULL;
return rq;
- Because
genctr was never incremented during cancellation and nvme_find_rq() does not verify whether rq is currently active (e.g., via blk_mq_request_started(rq) / rq->state == MQ_RQ_IN_FLIGHT), nvme_find_rq() successfully returns the pointer to the freed, idle request.
nvme_handle_cqe() proceeds to complete the request via nvme_try_complete_req(), which calls blk_mq_complete_request_remote(rq).
blk_mq_complete_request_remote() dereferences rq->mq_hctx to read rq->mq_hctx->nr_ctx, dereferencing NULL at offset 0x18e and crashing the kernel.
▶
▼
Last patch testing requests (1)
Sample crash report:
nvme 0000:00:02.0: enabling device (0201 -> 0203)
nvme nvme0: Disabling device after reset failure: -19
Unable to handle kernel paging request at virtual address dfff800000000031
KASAN: null-ptr-deref in range [0x0000000000000188-0x000000000000018f]
Mem abort info:
ESR = 0x0000000096000005
EC = 0x25: DABT (current EL), IL = 32 bits
SET = 0, FnV = 0
EA = 0, S1PTW = 0
FSC = 0x05: level 1 translation fault
Data abort info:
ISV = 0, ISS = 0x00000005, ISS2 = 0x00000000
CM = 0, WnR = 0, TnD = 0, TagAccess = 0
GCS = 0, Overlay = 0, DirtyBit = 0
[dfff800000000031] address between user and kernel address ranges
Internal error: Oops: 0000000096000005 [#1] SMP
Modules linked in:
CPU: 0 UID: 0 PID: 12 Comm: kworker/u8:0 Not tainted syzkaller #0 PREEMPT
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 11/06/2026
Workqueue: nvme-reset-wq nvme_reset_work
pstate: 83400005 (Nzcv daif +PAN -UAO +TCO +DIT -SSBS BTYPE=--)
pc : blk_mq_complete_request_remote+0xc4/0x858 block/blk-mq.c:1317
lr : blk_mq_complete_request_remote+0x88/0x858 block/blk-mq.c:1309
sp : ffff80008fa47900
x29: ffff80008fa47980 x28: ffff80008fa47900 x27: 1fffe00018d0167c
x26: 0000000000000001 x25: ffff0000c680b3ee x24: dfff800000000000
x23: ffff700011f48f20 x22: 1fffe00019195224 x21: ffff0000c8ca9124
x20: 000000000000018e x19: ffff0000c8ca9000 x18: 1fffe000359cf228
x17: 0000000000000003 x16: ffff8000898b7000 x15: ffff80008996b2e0
x14: 00000000001dca26 x13: 0000000000000001 x12: 0000000000000004
x11: 0000000000000018 x10: ffff8000884fd674 x9 : 1fffe00019195202
x8 : 0000000000000031 x7 : ffff8000828b3b38 x6 : 0000000000000000
x5 : 0000000000000000 x4 : 0000000000000001 x3 : ffff8000828b7a4c
x2 : 0000000000000000 x1 : ffff0000c1a00000 x0 : 0000000000000000
Call trace:
blk_mq_complete_request_remote+0xc4/0x858 block/blk-mq.c:1317 (P)
nvme_try_complete_req drivers/nvme/host/nvme.h:842 [inline]
nvme_handle_cqe drivers/nvme/host/pci.c:1822 [inline]
nvme_poll_cq+0x544/0xfd8 drivers/nvme/host/pci.c:1853
nvme_reap_pending_cqes drivers/nvme/host/pci.c:2311 [inline]
nvme_dev_disable+0x384/0x540 drivers/nvme/host/pci.c:3565
nvme_reset_work+0x510/0x69c drivers/nvme/host/pci.c:3727
process_one_work kernel/workqueue.c:3495 [inline]
process_scheduled_works+0x91c/0x1250 kernel/workqueue.c:3578
worker_thread+0x798/0xbd0 kernel/workqueue.c:3659
kthread+0x304/0x3d4 kernel/kthread.c:436
ret_from_fork+0x10/0x20 arch/arm64/kernel/entry.S:854
Code: 97cf97f3 f9400288 91063914 d343fe88 (38f86908)
---[ end trace 0000000000000000 ]---
----------------
Code disassembly (best guess):
0: 97cf97f3 bl 0xffffffffff3e5fcc
4: f9400288 ldr x8, [x20]
8: 91063914 add x20, x8, #0x18e
c: d343fe88 lsr x8, x20, #3
* 10: 38f86908 ldrsb w8, [x8, x24] <-- trapping instruction
* Struck through repros no longer work on HEAD.