{
  "finding_id": "DF-4646",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "nvme device teardown races the data path: nvme_pci_detach frees the namespace softcs and I/O queues with no serialization against in-flight bios (no UNLOADING flag set, no drain, no strategy guard; nvme_disk_detach neither drains nsc->bioq nor excludes nvme_disk_requeues/nvme_disk_callback). A bio dispatched past the cooked-cdev dead_dev_ops re-vector, parked on nsc->bioq, or re-dispatched by the admin-thread requeue enters nvme_get_request against a bzerod queue (nqe==0) and divides by zero. Fatal trap 18 at nvme_get_request+0x34 (divl 0x40(%r13)) reproduced 3/3 on fresh boots with 16 unprivileged-looking raw pread() spammers (no admin ioctls -- the data-path twin of DF-4529, which was the admin-ioctl path and whose validated fix did not cover in-flight bios). Fix (fix.diff v2) validated by full kernel rebuild: baseline 3/3 panic; patched battery (16-reader detach + 3 attach/detach cycles + concurrent opens) clean, readers get EBADF/ENODEV, no biowait hangs. Exploit ceiling assessed as panic/hang: the UAF-write side (devstat counters into freed nsc at fixed offsets) has limited control; no uid0 chain pursued beyond characterization.",
  "exploit_chain": "16 reader processes spam synchronous 128KB pread()s on /dev/nvme0..7 -> bios in flight / parked on nsc->bioq; sysctl hw.nvmebang.action=1 invokes the drivers own detach driver-method (identical to PCI hot-unplug) -> nvme_stop_admin_thread frees nsc under live I/O and the admin-thread exit frees+bzeros the I/O queues without a final completion poll -> in-flight/requeued bio enters nvme_get_request -> (tail+unsubmitted+1) % nqe with nqe==0 -> integer divide fault -> kernel panic (Fatal trap 18), DDB, system down. Residual silent side: nvme_disk_callback writes devstat counters into the freed softc; orphaned bios hang their owning threads forever.",
  "evidence": "panic.txt (Fatal trap 18 ... Stopped at nvme_get_request+0x34: divl 0x40(%r13),%eax -- pristine-kernel recapture, 3rd of 3 reproductions); VERDICT.md (full narrative incl. fix-v1 nvme_open NULL-deref lesson); run.log (patched-battery reader log: EBADF after destroy, 16/16 readers alive); build.log/base_build.log (patched + pristine kernel builds); nvio.c (rig), nvzombie.c (negative result: post-teardown read absorbed by dead_dev_ops); fix.diff (validated v2)",
  "kernel_refs": [
    "sys/dev/disk/nvme/nvme_disk.c:115",
    "sys/dev/disk/nvme/nvme_disk.c:172",
    "sys/dev/disk/nvme/nvme_disk.c:189",
    "sys/dev/disk/nvme/nvme_disk.c:379",
    "sys/dev/disk/nvme/nvme_disk.c:394",
    "sys/dev/disk/nvme/nvme_attach.c:470",
    "sys/dev/disk/nvme/nvme_admin.c:110",
    "sys/dev/disk/nvme/nvme_admin.c:185",
    "sys/dev/disk/nvme/nvme.c:446"
  ],
  "poc_changes": "Wrote the rig from scratch for this finding: nvio.c (16-process dynamic-discovery raw pread spammer; earlier draft used hardcoded /dev/nvme0..7 which broke after re-attach renumbered units to nvme8+), plus reused the DF-4529 nvmebang detach-trigger module unchanged. fix.diff authored in two iterations: v1 omitted the open/close/ioctl guards and the attach-side UNLOADING clear (fix validation caught nvme_open NULL-deref panic on the dying cdev); v2 is final and validated.",
  "attempts": 3,
  "guest_uname": "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 (baseline); fix kernel #1/#2 and pristine recapture #3 built on-guest with gcc 8.3",
  "runtime_sec": 2100,
  "guest_dirty": 0,
  "build_cmd": "cc -O2 -o /root/nvio nvio.c; (cd bang && make && kldload ./nvmebang.ko); [fix] cd /usr/src && make -j6 nativekernel KERNCONF=X86_64_GENERIC && make -j6 installkernel KERNCONF=X86_64_GENERIC",
  "run_cmd": "(nohup /root/nvio 16 >/root/nvio.log 2>&1 &); sleep 7; sysctl hw.nvmebang.action=1",
  "code_hash": "3d6491ee7ec52edd8d27b61a3c9cc144f1a3391d8c6d435fe4664b7ba985a697",
  "notes": "Serial-console capture of the multi-line trap block is partially interleaved on QEMUs -serial file backend (Fatal trap banner clobbered by concurrent output); the Stopped at line is clean in all three captures and byte-identical to DF-4529s documented signature (0x40(%r13) == queue->nqe). nvzombie.c documents the negative result that post-teardown fresh dispatches are absorbed by dead_dev_ops (EBADF). Guest reset with-src performed at end of run.",
  "recommended_fix": "Serialize teardown against the data path: set NVME_SC_UNLOADING at nvme_pci_detach entry (and clear it at attach); fail bios with EIO in nvme_strategy_core once unloading; drain nsc->bioq in nvme_disk_detach before destroy; NULL si_drv1 and guard open/close/ioctl/strategy; final completion-queue poll in the admin-thread exit before queue delete/free (validated: fix.diff).",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Validated by full kernel rebuild on the guest. Baseline (pristine): panic 3/3 under the exact rig. Patched v2: identical rig plus three attach/detach cycles with concurrent opens and reads -- no panic, clean EBADF/ENODEV for new I/O, no readers stuck in biowait, clean chip shutdown each cycle. Intermediate v1 (missing open/close/ioctl guards) was itself caught by the validation battery panicing in nvme_open on the dying cdev -- fixed in v2 before the final validated run.",
  "fix_evidence": "findings/poc/DF-4646/fix.diff (v2 final); build.log (patched build, nativekernel); base_build.log (pristine revert build); run.log (patched battery: pread error Bad file descriptor/Device not configured after ~7-8k iters, all 16 readers alive in nanslp); panic.txt (baseline recapture on pristine-rebuilt kernel); VERDICT.md Fix validation section."
}