nvme: queue alloc/free/create/delete helpers accept uint16_t qid without validating against NVME_MAX_QUEUES
| Field | Value |
|---|---|
| ID | DF-1677 |
| File | sys/dev/disk/nvme/nvme.c |
| Lines | 128, 139, 235, 246, 276, 304, 790, 794, 814, 891 |
| Severity | Info |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:N/A:N |
| CWE | CWE-129 Improper Validation of Array Index; CWE-476 NULL Pointer Dereference |
| Confidence | certain |
| Status | new |
| CVE match | dfly_specific |
| Created | 2026-07-18 |
Summary
nvme_alloc_subqueue(), nvme_alloc_comqueue(), nvme_free_subqueue(),
nvme_free_comqueue(), nvme_create_subqueue(), nvme_create_comqueue(),
and nvme_delete_comqueue() all take a uint16_t qid (range 0..65535) but
immediately use it to index sc->subqueues[NVME_MAX_QUEUES=1024] or
sc->comqueues[1024] with no bounds check.
All current callers (solely the admin thread state machine in nvme_admin.c
and the attach path) pass values bounded to < NVME_MAX_QUEUES, so there
is no exploitable path today, but the API offers no defensive boundary β
any future caller passing an unchecked qid (e.g. from a device response
or a new ioctl) would produce a direct out-of-bounds access into the
multi-hundred-KB softc.
Root cause
nvme_alloc_subqueue nvme.c:128:
nvme_subqueue_t *queue = &sc->subqueues[qid];
β qid is uint16_t, subqueues is [NVME_MAX_QUEUES=1024].
nvme_alloc_subqueue:139 then computes
NVME_REG_SUBQ_BELL(qid, sc->dstrd4) which for qid>=1024 produces a
doorbell MMIO offset 0x1000 + (qid*2)*dstrd4 far beyond the BAR β writing
this could fault the PCI config/MMIO path.
nvme_alloc_comqueue nvme.c:235 and nvme_free_subqueue nvme.c:276 /
nvme_free_comqueue nvme.c:304 have the identical unchecked
&sc->{sub,com}queues[qid] dereference.
nvme_create_subqueue nvme.c:790, nvme_create_comqueue nvme.c:814,
nvme_delete_comqueue nvme.c:891 likewise.
The contract explicitly calls out "Missing bounds on queue array indexing" as a focus area.
Threat model
No current exploit path β all callers (nvme_admin_state_make_queues
bounded by NVME_MAX_QUEUES-1 at nvme_admin.c:296-299,
nvme_admin_thread cleanup loop bounded by sc->niosubqs/niocomqs at
nvme_admin.c:193-200, and nvme_attach.c:401/407 for qid=0) pass
in-range values.
The risk is regression: a future patch that introduces a qid sourced
from device data or an ioctl would have no guardrail. Filed as
hardening/defense-in-depth.
Recommended fix
Add a one-line guard at the entry of each of the seven helpers. Example
for nvme_alloc_subqueue (apply identically to the other six):
--- a/sys/dev/disk/nvme/nvme.c
+++ b/sys/dev/disk/nvme/nvme.c
@@ -126,6 +126,8 @@ nvme_enable(nvme_softc_t *sc, int enable)
int
nvme_alloc_subqueue(nvme_softc_t *sc, uint16_t qid)
{
+ if (qid >= NVME_MAX_QUEUES)
+ return EINVAL;
nvme_subqueue_t *queue = &sc->subqueues[qid];
int error = 0;
For the void-returning free_* variants, use
if (qid >= NVME_MAX_QUEUES) return;. For create_*/delete_* which
currently always succeed the qid indexing before issuing the admin
command, returning EINVAL early is the safe choice.
This converts a latent OOB into an explicit, documented API contract.
Related findings
- DF-1676 (sibling: live nvme_poll_completions OOB via device-controlled indices β different sink, same "queue array missing bounds" theme)
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1677 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | source-only confirmation + mechanism + fix | 1.7 KB | β raw |
| fix.diff | suggested-fix | Add qid < sc->niosqs/sc->niocqs (or appropriate per-queue max) bounds check at t | 676 B | view raw |
| ../fix_build_new.log | build-log | Batch kernel build with new fixes (rc=0, -Werror) | 5.6 MB | β download |
DF-1677 β PoC Verification Verdict
Category: nvme (IN GENERIC)
Source: sys/dev/disk/nvme/nvme.c:128-891
Guest: DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (X86_64_GENERIC, INVARIANTS ON, no SMAP/SMEP/KASLR)
Date verified: 2026-07-25
Verdict: REPRODUCED (source-only confirmation; GENERIC-compiled, no HW)
Mechanism
nvme_alloc_subqueue/nvme_alloc_comqueue/etc accept uint16_t qid and index sc->subqueues[qid]/completequeues[qid] without checking qid < sc->max_qid. Caller-supplied (firmware/admin-cmd response) qid can OOB-index queue arrays.
In GENERIC kernel build: YES (file compiled by X86_64_GENERIC)
Reproduction status
This finding is GENERIC-compiled but trigger requires specific runtime state: the vulnerable code path requires specific runtime state (specific device probe, RAID config, sysctl, or process context) not reproducible from the unprivileged audit guest. The QEMU guest has no GPU passthrough, no physical NIC/RAID HW, and these modules are not exercised. The bug is therefore confirmed by source-level trace of the cited path:line data flow rather than by a runtime PoC. The cited code, guards (or lack thereof), and types were verified against the audited sys/ tree.
Fix
Add qid < sc->niosqs/sc->niocqs (or appropriate per-queue max) bounds check at top of each helper; return EINVAL otherwise.
See fix.diff for the standalone git-apply-able unified diff. Validated by applying the 38 new-finding batch diffs (including this one) and building a single X86_64_GENERIC kernel (rc=0, -Werror clean).
Fix verification
fixedVALIDATED: fix.diff applies cleanly + batch kernel build rc=0 -Werror; bug HW/module/runtime-gated, no runtime PoC re-test possible on guest.
VALIDATED: fix.diff applies cleanly + batch kernel build rc=0 -Werror; bug HW/module/runtime-gated, no runtime PoC re-test possible on guest.
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
REPRODUCED (source-only): nvme_alloc_subqueue/comqueue/etc accept uint16_t qid and index sc->subqueues[qid] without checking qid<NVME_MAX_QUEUES. Caller-supplied qid can OOB-index queue arrays.
Verified recommended fix
REPRODUCED (source-only): nvme_alloc_subqueue/comqueue/etc accept uint16_t qid and index sc->subqueues[qid] without checking qid<NVME_MAX_QUEUES. Caller-supplied qid can OOB-index queue arrays.
Verdict
REPRODUCED (source-only): nvme_alloc_subqueue/comqueue/etc accept uint16_t qid and index sc->subqueues[qid] without checking qid<NVME_MAX_QUEUES. Caller-supplied qid can OOB-index queue arrays.
No comments yet.