β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4697

nvme(4): duplicate nsid in the device-supplied ACTIVE NAMESPACE LIST re-attaches a live namespace β€” second nvme_disk_attach() bzeroes the live struct disk inside the global disklist (self-loop), double-inserts devstat, creates duplicate /dev/nvmeN cdevs β†’ deterministic kernel panic on any later disklist walk

Summary

The nsid scan trusts the device list without dedup; a repeated nsid re-runs the whole attach on the live object: bioq/lock re-init on live objects, devstat double-insert into the global device_statq, disk_create bzero()ing the live struct disk while linked in the global disklist then double-LIST_INSERT_HEAD (self-loop), plus a duplicate same-name cdev. REPRODUCED on the QEMU-nvme guest via a KLD injecting exactly the duplicate decision and calling the REAL nvme_disk_attach on the REAL live nsc: panic 'Bad link elm ... next->prev != elm' in disk_enumerate, 2/2 fresh boots, triggerable by unpriv `sysctl kern.disks`. Fix validated on a rebuilt kernel: refusal message, clean walks, clean teardown. Crafted/firmware-compromised controller model (trivially emulated in a virt stack). Dedupe + idempotent-attach + cdev-clear fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4697 Β· 12 files
FileTypeDescriptionSize
df4697.c β€” 3.6 KB view raw
Makefile β€” 144 B ↓ download
build.sh β€” 120 B view raw
run.sh β€” 147 B view raw
build.log β€” 3.7 KB view raw
run.log β€” 339 B view raw
run.2.log β€” 45 B view raw
run.patched.log β€” 250 B view raw
panic.txt β€” 907 B view raw
panic.2.txt β€” 819 B view raw
env.txt β€” 1.2 KB view raw
fix.diff β€” 1.8 KB view raw

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff applied to guest /usr/src, kernel rebuilt (nativekernel KERNCONF=X86_64_GENERIC, #1 22:14) and booted; identical PoC run: guard printed 'nvme0: namespace 1 already attached', cdev unchanged, sysctl kern.disks/kern.devstat.all rc=0, list identical to baseline, normal single detach still works. Baseline panic 2/2 vs patched 0/1.

findings/poc/DF-4697/run.patched.log; serial log lines 'nvme0: namespace 1 already attached' + cdev(after)=cdev(before); patched kernel uname '#1: Tue Sep 22 22:14:04 UTC 2026'
↓ fix.diffper-fix-DF-4697

Confirmed kernel references

Detail

Exploit chain

crafted/misbehaving NVMe controller returns active-ns list [1,1] (or any duplicate) -> admin thread double-attaches nsid 1 (nvme_admin.c:680) -> global disklist self-loop + duplicate cdev + devstat queue corruption -> any unprivileged 'sysctl kern.disks' panics the kernel (deterministic); teardown leaves a stale /dev/nvmeN cdev pointing at the destroyed struct disk (UAF surface).

Evidence (decisive lines)

panic.txt + panic.2.txt (identical 'Bad link elm' signatures, disk_enumerate<-sysctl_disks trace, 2 fresh boots); run.log/run.2.log (second attach returned with cdev(after)!=cdev(before) = duplicate cdev); run.patched.log + 'namespace 1 already attached' in serial log (fix works, walks rc=0); fix.diff

PoC changes

seed sketch replaced entirely: wrote KLD (df4697.c) that locates the live controller via the driver's own globals (nvme_sc_list/nvme_master_lock, nvme_attach.c:50-51) and performs the second nvme_disk_attach() exactly as the buggy scan does; Makefile fixed to bsd.kmod.mk + generated bus_if/device_if/pci_if/pcidevs headers; module rebuilt after every panic (fs sync) loss.

Verified recommended fix

Dedupe the device-supplied active-ns scan in nvme_admin_state_identify_ns() and make nvme_disk_attach() idempotent (see fix.diff).

Verdict

Duplicate nsid in the device-controlled ACTIVE NAMESPACE LIST makes nvme_admin_state_identify_ns() call nvme_disk_attach() twice on the same live nvme_softns_t; the second disk_create() bzero()s the live struct disk while it is linked in the global disklist and re-inserts the same node (self-loop), devstat_add_entry() double-inserts into the global device_statq, and a duplicate /dev/nvmeN cdev is created. Any later disklist walk (sysctl kern.disks, unprivileged) panics with 'Bad link elm next->prev != elm'. Reproduced 2/2 on fresh boots of the QEMU-nvme guest: because stock QEMU cannot emit a duplicated nsid list, the PoC module injects only the duplicate-decision and then calls the REAL nvme_disk_attach() on the REAL live namespace (nvme_admin.c:680 frame-for-frame); the panic trace is pure unmodified driver/kernel code (disk_enumerate <- sysctl_disks <- sys___sysctl). fix.diff (scan dedupe + attach idempotence) validated on-guest with a rebuilt kernel: refusal message, identical disk list, clean walks and clean teardown, no panic.