# PoC DF-4529 — nvme(4) detach vs in-flight NVMEIOCGETLOG: use-after-free → kernel div-by-zero panic (REPRODUCED)

## What this proves

`nvme_pci_detach()` tears down and frees the admin queue while user
threads holding an open namespace-device descriptor can still be inside
`nvme_getlog_ioctl()` → `nvme_get_admin_request()` → `nvme_get_request()`.
Nothing blocks new ioctls, drains in-flight ones, or takes `sc->ioctl_lk`
on the detach side (`NVME_SC_UNLOADING` is checked in `nvme_open()` but is
**never set anywhere**; `sc->opencnt` is tracked but never consulted).

When the race hits, `nvme_free_subqueue()` (nvme.c:298) has `bzero()`'d the
submission queue (`nqe == 0`) while the ioctl thread evaluates the
ring-full check in `nvme_get_request()`:

```c
if ((queue->subq_tail + queue->unsubmitted + 1) % queue->nqe ==   /* nvme.c:446 */
    queue->subq_head) {
```

`% queue->nqe` with `nqe == 0` → integer divide fault → **kernel panic**.
Observed twice (deterministic with the rig below):

```
Fatal trap 18: integer divide fault while in kernel mode
instruction pointer = 0x8:0xffffffff80310904
current process      = 934        (an /root/nvspam spammer)
Stopped at nvme_get_request+0x34: divl 0x40(%r13),%eax
```

`0x40(%r13)` is `queue->nqe` (offset 0x40 in `struct nvme_subqueue`,
nvme.h:118-155). `panic.txt` holds the full trap block from the serial
console.

## Environment

* Private QEMU/KVM instance of the audit guest (dfbsd-qemu/vm_nvme.sh):
  the stock virtio-boot guest plus `-device nvme,id=nv0,serial=AUDNVME,
  msix_qsize=65,max_ioqpairs=8` and 8 `-device nvme-ns` namespaces backed
  by null-co blockdevs (the guest's stock config has no NVMe device).
* `hw.nvmebang.action` test module (nvmebang.c, in this pack) invokes the
  nvme driver's own attach/detach driver-methods on unit 0 on demand —
  a deterministic, repeatable stand-in for PCI hot-unplug / surprise
  removal, which runs the identical `nvme_pci_detach()` path.
* `nvspam` (root): loops `ioctl(NVMEIOCGETLOG)` on `/dev/nvme*`, i.e. the
  raw-admin-command ioctl, ~10-25k calls/s per process.

## Build

In the guest (as root):

```sh
# spammer + instrumented probers
cc -O2 -o /root/nvspam  nvspam.c
cc -O2 -o /root/nvslow  nvslow.c
cc -O2 -o /root/nvgetlog nvgetlog.c
# trigger module
cd /root/poc/bang   # Makefile: SYSDIR=/usr/src/sys, SRCS=nvmebang.c
                   #           device_if.h bus_if.h pci_if.h
make && kldload ./nvmebang.ko
```

## Run (exact rig)

```sh
# 6 spammers hammering the admin ioctl path
for i in 1 2 3 4 5 6; do /root/nvspam > /root/sp$i.log 2>&1 & done
sleep 5
# one device removal
sysctl hw.nvmebang.action=1
```

## Expected

Unfixed kernel: panic within seconds — `Fatal trap 18` /
`nvme_get_request+0x34 divl 0x40(%r13),%eax` (see panic.txt; reproduced
2/2 fresh boots, first detach each time).

Patched kernel (fix.diff applied, kernel rebuilt):
`nvme0: detached` in dmesg, **no panic**, spammers keep running and see
only graceful ioctl errors (ENXIO) afterwards. Repeated detach/attach
cycles stay clean.
