Unvalidated device-controlled shift in namespace block-size computation
Summary
nsc->blksize computed as 1<<lbafmt->sect_size where lbafmt->sect_size is device-controlled uint8_t from Identify Namespace DMA response with no range validation. For sect_size in [32 255] shift is UB (C11). For sect_size==31 yields INT_MIN becomes 2^31 in uint32. Resulting garbage blksize used as divisor and in byte-count products in nvme_disk.c enabling corrupted geometry nonsensical media sizes and integer overflow in nlba*blksize I/O sizing. [VERIFIED pass2: reproduced - device picks blksize 0x80000000 or 16 driving UB 1-lt-lt-n into nvme_disk_attach; power-of-two 9..12 reject fix A/B attach skipped]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2513 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 993 B | β raw | |
| VERDICT.md | β | 1.9 KB | β raw | |
| build.sh | β | 99 B | view raw | |
| run.log | β | 375 B | view raw | |
| env.txt | β | 963 B | view raw | |
| fix.diff | β | 623 B | view raw | |
| manifest.json | β | 759 B | view raw | |
| verdict.json | β | 2.3 KB | view raw |
DF-2513 -- Unvalidated device-controlled shift in nvme namespace block-size
Verification used the shared rig tree at /root/rigs on the audit guest (see manifest). The rig for this finding: dfv_nvme (rig.c = harness, gen.sh = verbatim extractor, dfv_*_extract.h = generated production text). The decisive outputs below were captured live on the guest during the 2026-10-24 verification session; full session logs are summarized per run.
Build
sh build.sh # extracts verbatim driver text + builds the KLD
Run (guest, root)
kldload ./dfv_rig.ko
kldload dfv_nvme.ko; for s in 9 31 36 63; do sysctl kern.dfv_nvme_run=$s >/dev/null; sysctl -n kern.dfv_nvme_status; done
Expected
run.log: blksize 512/2147483648/16/2147483648 for sect_size 9/31/36/63; fixed: attach skipped.
Fix validation
fix.diff rejects sect_size outside 9..12 and skips the attach; A/B shows no namespace published.
Verdict: reproduced / impact=corruption (see verdict.json, VERDICT.md)
DF-2513 verdict -- Unvalidated device-controlled shift in nvme namespace block-size
status: reproduced reproduced: 1 impact: corruption confidence: certain attempts: 2 runtime_sec: 500
The verbatim nvme_admin_state_identify_ns computes nsc->blksize = 1 << lbafmt->sect_size (nvme_admin.c:673-675) with sect_size a device-controlled uint8_t from the Identify Namespace DMA and no range check. The rig refills the crafted Identify response at the DMA-completion boundary (after the drivers bzero of req->info - the first version filled too early and the bzero wiped it). Results: sect_size=9 -> 512 (legal control); 31 -> 0x80000000 (2GiB blocks); 36 -> 16 (shift count masked to 5 bits - C11 UB); 63 -> 0x80000000. The garbage blksize is what nvme_disk_attach publishes as the namespace media block size (geometry/media-size corruption; divisor in I/O sizing). Fixed source (reject formats outside 9..12, skip attach) A/B: attach count 0, no garbage published.
Evidence (decisive lines)
DF2513[sect_size=9]: nvme_disk_attach saw blksize=512 DF2513[sect_size=31]: nvme_disk_attach saw blksize=2147483648 DF2513[sect_size=36]: nvme_disk_attach saw blksize=16 DF2513[sect_size=63]: nvme_disk_attach saw blksize=2147483648 --- fixed source A/B --- DF2513[sect_size=31]: nvme_disk_attach saw blksize=3735928559 (attach count 0) <- attach skipped, nothing published
Exploit chain / privesc assessment
hostile/faulty NVMe controller (PCI trust); geometry corruption; no memory-safety violation by itself.
Fix + A/B
fix.diff rejects sect_size outside 9..12 and skips the attach; A/B shows no namespace published.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedConfirmed kernel references
Detail
Exploit chain
hostile/faulty NVMe controller (PCI trust); geometry corruption; no memory-safety violation by itself.
Evidence (decisive lines)
run.log: blksize 512/2147483648/16/2147483648 for sect_size 9/31/36/63; fixed: attach skipped.
PoC changes
DMA refill hook moved into the interposed wait/completion (bzero ordering); first rig version was self-defeating.
Verified recommended fix
validate lbafmt->sect_size (9..12) before the shift
Verdict
The verbatim nvme_admin_state_identify_ns computes nsc->blksize = 1 << lbafmt->sect_size (nvme_admin.c:673-675) with sect_size a device-controlled uint8_t from the Identify Namespace DMA and no range check. The rig refills the crafted Identify response at the DMA-completion boundary (after the drivers bzero of req->info - the first version filled too early and the bzero wiped it). Results: sect_size=9 -> 512 (legal control); 31 -> 0x80000000 (2GiB blocks); 36 -> 16 (shift count masked to 5 bits - C11 UB); 63 -> 0x80000000. The garbage blksize is what nvme_disk_attach publishes as the namespace media block size (geometry/media-size corruption; divisor in I/O sizing). Fixed source (reject formats outside 9..12, skip attach) A/B: attach count 0, no garbage published.
No comments yet.