# VERDICT — DF-4646

**Status: reproduced (impact: panic — use-after-free-driven kernel
divide-by-zero). Reproduced 3/3 on fresh boots. Fix validated by full
kernel rebuild (baseline panics 3/3 → patched: 0 panics across a
16-reader detach, three attach/detach cycles, and concurrent opens).**

## Root cause

The teardown of an nvme controller frees everything the data path still
touches, with no serialization against it:

1. `nvme_pci_detach()` (nvme_attach.c:470-517) never sets
   `NVME_SC_UNLOADING` (only `nvme_open()` checks it, nvme_disk.c:132 —
   dead flag, as DF-4529 documented), never drains in-flight I/O, and
   never takes a lock the strategy path honors.
2. `nvme_stop_admin_thread()` (nvme_admin.c:110-127) — while the admin
   thread is still running and IO interrupts are still installed — runs
   `nvme_disk_detach(nsc); kfree(nsc);` for every namespace.  Bios parked
   on `nsc->bioq` (submitted while the submission ring was full,
   nvme_disk.c:379-389) are orphaned; the admin thread's own
   `nvme_disk_requeues()` (nvme_disk.c:189-213) can hold `nsc->lk` on a
   namespace that is freed concurrently.
3. The admin thread's exit path (nvme_admin.c:185-197) deletes and frees
   the I/O queues without a final completion poll; a late completion
   delivers `nvme_disk_callback()` → `devstat_end_transaction_buf()`
   on the freed `nsc` (nvme_disk.c:394-430), and an MSI-X interrupt on a
   freed/bzero'd completion queue hits `KKASSERT(comq_tail < nqe)` in
   `nvme_poll_completions()` (nvme.c:575).
4. A read/write bio dispatched past the cooked-cdev re-vector (or
   re-dispatched by the requeue drain) enters `nvme_get_request()`
   against a `bzero()`'d submission queue: `(tail+unsubmitted+1) % nqe`
   with `nqe == 0` → **integer divide fault** (nvme.c:446).

Observed fatal manifestation (3/3):

```
nvmebang: DETACH method on nvme0...
Fatal trap 18: Stopped at      nvme_get_request+0x34:  divl    0x40(%r13),%eax
db>
```

`0x40(%r13)` = `queue->nqe`.  This is the data-path twin of DF-4529
(admin-ioctl path): the trigger rig here uses only `pread()` — no admin
ioctls — so the two findings are independently reachable manifestations
of the same missing teardown serialization.  DF-4529's validated fix
guarded open/close/ioctl/strategy against a NULLed `si_drv1` but did not
address in-flight bios, the requeue race, or the queue teardown order —
that residual class is this finding.

## Reproduction

Rig: 16 reader processes (`nvio`) issuing continuous synchronous 128 KB
raw `pread()`s across nvme0..7 (null-co-backed, ~7k reads/s total);
`sysctl hw.nvmebang.action=1` invokes the driver's own detach
driver-method (the exact code PCI hot-unplug runs).  Run 1 and Run 2:
panic on first detach (Run 2 banner captured in full — panic.txt shows
the Run 3 recapture on a pristine-rebuilt kernel; all three identical).
`nvzombie.c` additionally documents the NEGATIVE result that a purely
post-teardown dispatch is absorbed by `dead_dev_ops` (EBADF) — the bug
is the in-flight window.

## Exploit-chain assessment

The reliable primitive is the divide-by-zero panic plus orphaned-bio
hangs (unkillable-style blocked I/O).  The UAF-write side
(`nvme_disk_callback` into freed `nsc` via `devstat_end_transaction_buf`,
fixed offsets inside the freed chunk, counter-like values) and the
freed-queue `lockmgr()` use offer heap-corruption potential under heap
pressure, but with fixed offsets/limited value control we assess the
realistic ceiling as denial of service (panic / hung I/O), matching the
DF-4529 precedent.  No uid=0 chain was pursued beyond this
characterization: the deterministic outcome is a fault, not a
controllable allocation.

## Fix validation (fix.diff — v2, final)

* v1 (strategy/requeue/bioq guards + UNLOADING flag + final comq poll)
  killed the original panic but introduced its own: NULLing `si_drv1`
  without guarding `nvme_open()` produced `nvme_open+0xb movq
  (%rax),%rdx` NULL-deref on a concurrent open of the dying cdev —
  caught by the validation battery, fixed in v2 by adding the
  open/close/ioctl guards and clearing `NVME_SC_UNLOADING` at attach
  (nvme_attach.c:115).
* v2 battery on the rebuilt kernel (`make nativekernel`): 16 readers ×
  detach — no panic, all readers survived with EBADF/ENODEV on new I/O;
  three further attach/detach cycles with concurrent opens and reads —
  clean; no readers stuck in biowait (`ps` shows all in nanslp retry
  loops); `dmesg` shows normal chip shutdown each time.
* Baseline re-verified after reverting the guest source to pristine and
  rebuilding: same panic signature on the first detach under load
  (panic.txt).

Build logs: build.log (patched v2 build), base_build.log (pristine
rebuild).  Run logs: run.log (patched-battery reader log), nvio*.log.

## Kernel references

* sys/dev/disk/nvme/nvme_disk.c:115-122 (`nvme_disk_detach` — no drain, no exclusion)
* sys/dev/disk/nvme/nvme_disk.c:172-181 (`nvme_strategy` — no teardown guard)
* sys/dev/disk/nvme/nvme_disk.c:189-213 (`nvme_disk_requeues` — races kfree)
* sys/dev/disk/nvme/nvme_disk.c:379-389 (bioq park path — orphaned at kfree)
* sys/dev/disk/nvme/nvme_disk.c:394-430 (`nvme_disk_callback` — freed nsc use)
* sys/dev/disk/nvme/nvme_attach.c:470-517 (`nvme_pci_detach` — no serialization)
* sys/dev/disk/nvme/nvme_admin.c:110-127 (`kfree(nsc)` under live I/O)
* sys/dev/disk/nvme/nvme_admin.c:185-197 (queue delete/free without final poll)
* sys/dev/disk/nvme/nvme.c:446 (`% queue->nqe` div0 site)

## 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.
