# DF-2302 VERDICT — NULL deref in fwohci_db_free after partial db_init

* finding: DF-2302, fwohci.c:1236-1241 (bus_dmamap_create failure ->
  faked FWOHCI_DBCH_INIT -> fwohci_db_free) and fwohci.c:1166-1174
  (the unguarded loop)
* status: **reproduced** (kernel panic, fatal trap 12, fault VA 0x18)
* impact: panic (denial of service; requires memory pressure in the
  32-bit DMA window during FireWire channel init on real hardware)
* confidence: certain

## Reproduction narrative
1. Rig: fwohci.c TU harness (same as DF-2300); fabricated parent
   bus_dma tag with a 4 MB lowaddr models the >4 GB-RAM host on which
   the stock fwohci tag (lowaddr = BUS_SPACE_MAXADDR_32BIT,
   fwohci.c:1197) must bounce. All busdma code executed is the real
   stock platform code (busdma_machdep.c, vm_contig.c).
2. `fwpoc ctl memeat 2 3`: eat the whole [0,4MB) window with
   M_WAITOK contigmalloc (same 3-pass reclamation power the map_create
   path has), free 2 pages, run REAL fwohci_db_init on arrs.
3. Rounds 1..56: db_init succeeds; each round permanently converts
   1-2 window pages into busdma-zone bounce pages (ate declines
   128 -> 0 monotonically, run.log).
4. Final round (ate=0, freed=0): inside the real fwohci_db_init loop
   the FIRST bus_dmamap_create of dbch->dmat hits
   `alloc_bounce_pages -> contigmalloc -> NULL`
   (busdma_machdep.c:412, vm_contig.c:560-580) -> ENOMEM ->
   fwohci.c:1237 prints "bus_dmamap_create failed" ->
   fwohci.c:1238 fakes INIT -> fwohci_db_free ->
   fwohci.c:1166 loop starts with db_tr = STAILQ_FIRST = NULL
   (queue empty, K=0) -> first body evaluates `db_tr->dma_map`
   (compiled first; fault VA 0x18 = offsetof(struct fwohcidb_tr,
   dma_map), fwohcireg.h:330-337) -> fatal trap 12 -> panic.
   Serial capture verbatim in panic.txt (backtrace:
   fwohci_db_free.part.7 <- fwoctl_ioctl; the .part.7 is GCCs clone
   of the static fwohci_db_free from the TU-compiled verbatim
   fwohci.c).

## K-quantification (new line-accurate knowledge from this run)
busdma_machdep.c:412-425: after the first successful create sets
BUS_DMA_MIN_ALLOC_COMP, the `else { error = 0; }` clears subsequent
alloc_bounce_pages failures. Only the FIRST map_create of a tag can
return ENOMEM. Hence on this tree the reachable defect form is K=0
(empty queue, fault at iteration 0). The originally-filed K>0 form
(partial queue, NULL after the last queued entry) is masked by the
busdma quirk today but is the same unguarded loop; any future busdma
change re-arms it. The fix guards both forms.

## Fix validation (A/B)
fix.diff:
* loop condition `db_tr != NULL && idb < dbch->ndb`
  (stops at the real queue tail),
* `if (db_tr != NULL) kfree(db_tr, M_FW);` on the tail free — WITHOUT
  this, the fixed loop merely moves the panic to kfree(NULL)
  (kern_slaballoc.c:1409 panics on NULL), which I verified while
  authoring the patch.
Fixed kernel (FWO #4): identical trigger (full window exhaustion +
memeat rounds) -> "bus_dmamap_create failed" printed, db_free unwinds
without panic, guest stays up, repeated rounds keep surviving
(run_fixed.log). Residual K=0 leak of the db_tr array documented in
README (db_init loses the array base across the loop).

## Reachability
Hardware + operator-group (channel setup) + 32-bit-DMA-window memory
pressure. Unprivileged users contribute the pressure; the panic is
local DoS. No privilege escalation path — NULL-deref only.

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