DF-4529 / verdict.json
{ "finding_id": "DF-4529", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Reproduced deterministically (2/2 fresh boots): nvme_pci_detach() frees/bzero()s the admin submission queue while a user thread holding an open namespace fd is inside nvme_get_request() via NVMEIOCGETLOG; the ring-full check (nvme.c:446) executes % queue->nqe with nqe==0 -> Fatal trap 18 integer divide fault at nvme_get_request+0x34 (divl 0x40(%r13),%eax), current process = the ioctl spammer. Nothing on the detach path takes sc->ioctl_lk or sets NVME_SC_UNLOADING (the flag is checked in nvme_open but never set anywhere in the tree), and opencnt is never drained, so any device removal (hot-unplug, surprise removal, VM device_del) racing an open descriptor corrupts the kernel: div0 panic at best, silent reads/writes into the kfreed reqary/completion ring at worst. Fixed kernel (fix.diff: ioctl_lk held across teardown + UNLOADING gate + nsc->sc NULLed before kfree + nvme_ioctl NULL guard) rebuilt and re-tested with the identical rig: no panic, clean detach.", "exploit_chain": "", "evidence": "panic.txt (full trap block, both runs), README.md rig (6x nvspam NVMEIOCGETLOG loop + sysctl hw.nvmebang.action=1 driver-detach trigger), VERDICT.md (path:line trace), fix.diff, fix_build.log, fix_run.log", "kernel_refs": [ "sys/dev/disk/nvme/nvme.c:446", "sys/dev/disk/nvme/nvme.c:273-299", "sys/dev/disk/nvme/nvme_ioctl.c:48-60", "sys/dev/disk/nvme/nvme_attach.c:470-517", "sys/dev/disk/nvme/nvme_disk.c:132", "sys/dev/disk/nvme/nvme_admin.c:117-126" ], "poc_changes": "No seed PoC existed. Built: nvgetlog/nvspam/nvslow (NVMEIOCGETLOG userspace, struct replicated from nvme_ioctl.h), nvmedetach/nvmebang klds (device_detach / direct driver attach+detach method invocation for deterministic, repeatable removal). QEMU relaunched with -device nvme + 8 nvme-ns namespaces (vm_nvme.sh).", "attempts": 8, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 x86_64 (baseline) / #1 fixed rebuild", "runtime_sec": 2400, "guest_dirty": 1, "build_cmd": "guest: cc -O2 -o /root/nvspam nvspam.c; kld build of nvmebang.ko via kmod.mk (SYSDIR=/usr/src/sys)", "run_cmd": "6x /root/nvspam & ; sleep 5; sysctl hw.nvmebang.action=1", "code_hash": "5bce4ea0e26e05a841fea599559a209f8aaf0699ac529ff8ec76667477f6c7b5", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Baseline (unfixed): Fatal trap 18 nvme_get_request+0x34 div0 on first detach under load, 2/2 fresh boots (deterministic). fix.diff v1 (ioctl_lk held across teardown + UNLOADING gate + nsc->sc NULL): kernel rebuilt+md5-verified, identical single-detach rig -> clean detach, no panic, spammers continue. Aggressive 6x attach/detach cycling then surfaced the same softns-lifetime class via nvme_open (panic.open.txt) -> fix.diff v2 additionally NULLs cdev->si_drv1 at disk destroy and guards every dispatch entry; on v2 the getlog div0 remains eliminated, but teardown-window zombie-cdev dispatch can still fault one instruction earlier (nvme_ioctl+0xe, residual_fault.txt). Verdict: the demonstrated bug is fixed; the broader cdev-outlives-softns class requires refcounting softns per open descriptor upstream-grade (documented in VERDICT.md).", "fix_evidence": "fix_build.log, fix_run.log, panic.txt (baseline div0), panic.open.txt (v1 residual via open), residual_fault.txt (v2 residual via zombie dispatch)", "notes": "Trigger caveat: reproducing the removal deterministically required the nvmebang test module because the audit guest (i440FX, DFly 6.5) does not act on QEMU device_del ACPI PCI hotplug events; the module invokes the identical nvme_pci_detach() driver method a real removal runs. The ioctl side needs an open raw-disk fd (root; an operator-group test user got EPERM at open, contrary to DF-2217s premise). A first fix-validation run panicking identically was traced to a stale kernel being booted (objdir conflict); verified via symbol addresses (runtime detach 0xffffffff80311920 = old kernel vs /boot/kernel/kernel 0xffffffff80311bc0 = fixed) and re-validated after a verified clean rebuild.", "recommended_fix": "Serialize teardown against the ioctl path: set NVME_SC_UNLOADING and hold sc->ioctl_lk across the admin queue free in nvme_pci_detach(); refuse new getlog ioctls while unloading; NULL nsc->sc before kfree." } |