# DF-2302 — fwohci_db_free NULL deref on partial bus_dmamap_create failure

## What this is
Verification pack for finding DF-2302 (`sys/bus/firewire/fwohci.c:1236`
failure path -> `fwohci_db_free`, fwohci.c:1157-1182). Low / local DoS
panic. Claim: if `bus_dmamap_create()` fails at loop iteration K,
fwohci_db_init fakes FWOHCI_DBCH_INIT (fwohci.c:1238) and calls
fwohci_db_free, whose loop (fwohci.c:1166-1167) iterates `idb < ndb`
over a queue that only holds K entries -> STAILQ_NEXT yields NULL ->
`db_tr->buf`/`db_tr->dma_map` dereference of NULL -> kernel panic.

## Verdict (short)
REPRODUCED — kernel panic through 100% real code:

```
bus_dmamap_create failed                              <- fwohci.c:1237
Fatal trap 12: page fault while in kernel mode
fault virtual address = 0x18                          <- NULL + offsetof(fwohcidb_tr, dma_map)
fwohci_db_free.part.7() at ...  <- fwohci.c:1166-1174
fwoctl_ioctl() ...
panic: page fault
```

The panic (panic.txt) came from the REAL `fwohci_db_init` ->
REAL `bus_dma` failure -> REAL `fwohci_db_free`, on the verbatim
fwohci.c TU harness kernel. Trigger: the 32-bit DMA bounce window
(4 MB, modeling a >4 GB-RAM host where the OHCI 32-bit window is a
scarce resource) exhausted so `alloc_bounce_pages ->
contigmalloc` fails (busdma_machdep.c:412) inside bus_dmamap_create.

## Rig mechanics (how the real failure was produced)
* The fabricated parent bus_dma tag has lowaddr=0x400000 (4 MB). On a
  >4GB-RAM host the stock fwohci tag (lowaddr=BUS_SPACE_MAXADDR_32BIT,
  fwohci.c:1197) has exactly this character: bounce pages must come
  from below lowaddr.
* `fwpoc ctl memeat F ch`: pins EVERY free page of [0,4MB) with
  contigmalloc (M_WAITOK — same 3-pass reclamation power as the
  bus_dmamap_create path, sys/vm/vm_contig.c:300-376), frees F pages,
  then calls the real fwohci_db_init on the channel.
* Repeated rounds convert window pages into permanent zone bounce
  pages (each successful db_init permanently parks 1-2 pages in the
  busdma zone). After ~57 rounds the window is fully pinned (ate=0);
  the final round runs db_init with zero free window pages -> the
  first bus_dmamap_create fails -> db_free -> panic (run.log has the
  ate-by-round history: 128 -> 0).

## K-quantification (DF 6.5 busdma behavior found during verification)
`bus_dmamap_create()` only returns ENOMEM while
BUS_DMA_MIN_ALLOC_COMP is clear — after the first successful create
the `else { error = 0; }` at busdma_machdep.c:423-425 clears later
alloc failures. Consequence: on this tree the reachable form of the
bug is **K=0** (first create fails, queue empty, first loop body
derefs NULL — fault VA 0x18 = offsetof(dma_map)). The K>0 form
(partial queue) needs a first-create success followed by a later
failure, which current busdma masks — but the loop defect is
identical, and the fix guards both.

## Reachability honesty
Requires FireWire hardware + memory pressure in the 32-bit DMA window
during channel (re)initialization (controller init, iso channel
setup). Attacker role: unprivileged users can generate the memory
pressure; the init window belongs to open/enable paths reachable from
the operator group. Severity Low as filed (panic/DoS only).

## Fix
fix.diff: NULL-safe loop (`db_tr != NULL && idb < dbch->ndb`) plus a
guard on the tail `kfree(STAILQ_FIRST(...))` (kfree(NULL) panics on
DF, kern_slaballoc.c:1409 — the tail free otherwise just moves the
panic). Residual (documented): the K=0 error path leaks the one
kmalloc'd db_tr array because db_init loses its base pointer across
the loop; fixing that cleanly requires keeping the array head in
db_init (not done to keep the patch minimal).
A/B validated: fixed kernel survives the identical trigger with a
clean unwind (see run_fixed.log).
