# DF-2301 VERDICT — print_db off-by-one (fwohci.c:2347)

* finding: DF-2301, fwohci.c:2347 (`for (i = 0; i <= max; i++)` in
  print_db, fwohci.c:2322-2404; caller dump_db fwohci.c:2311 passes
  max = dbch->ndesc)
* status: **reproduced** (OOB read of one 16-byte descriptor past the
  ndesc-entry block, plus the compounding db[i+1] ST2 read, printed to
  console by an operator-group-triggered DUMPDMA)
* impact: leak (console/dmesg disclosure of the out-of-bounds 16 bytes;
  latent fault if the read lands on an unmapped page — not realizable
  on x86_64 direct-map in this configuration)
* confidence: certain

## Mechanical proof (baseline kernel FWO #2, run.log)
Setup (root, via rig): real `fwohci_db_init` on atrq with ndesc=8,
ndb=64, psize=4096 -> fwdma_malloc_multiseg allocates 2 segments of
ssize=4096, esize=128, 32 elements/segment (rig prints the layout:
seg0=0xfffff80117c14000 seglast=0xfffff80117c15000).
Descriptors of element 5 and 63 fabricated as INPUT_MORE|KEY_ST3|
INTERRUPT_TRUE (no DEVICE key, no BRANCH_ALWAYS, no OUTPUT_LAST/
INPUT_LAST -> print_db runs to loop exhaustion); canary planted in
element 6 db[0] = {0xcafe0001, 0x11111111, 0x22222222, 0x33333333}.
ATQ command-pointer register (emulated MMIO 0x18c) set to the target
element bus_addr so dump_db's match (fwohci.c:2287) selects it.

opuser -> `DUMPDMA(0)`:

1. element-5 match: 9 rows. Row 8 = `030a5280  ST2 ALL ALL 1 11111111
   22222222 3333:3333` — exactly the canary at 0xfffff80117c14300
   (= element5 + esize = &db[8]), byte-for-byte what the rig KVA peek
   returned for that address. The canary cmd key bits also triggered
   the ST2 branch (fwohci.c:2377): the line
   `0x00000000 0x00000000 0x00000000 0x00000000` is db[9] — the
   second, compounding OOB (fwohci.c:2379), after which i++ at
   fwohci.c:2400 skips the loop past the bound.
2. element-63 match (last element of the last segment): row 8 =
   `030a6f80 OUTM ST0 NEV NEV 0 00000000 00000000 0000:0000`,
   reading at 0xfffff80117c16000 = seglast.va + 4096 — one full
   descriptor block beyond the second (last) bus_dma segment
   allocation; rig KVA peek of that address returns the same zeros.

Both DUMPDMA calls returned rc=0 for the operator-group user; output
lands in dmesg/console (kprintf at fwohci.c:2352).

## Why row 8 exists
`i <= max` iterates ndesc+1 times over db[0..ndesc] while the block
(fwdma element) holds ndesc entries; there is no NULL or bounds guard.

## Fix validation (A/B)
fix.diff: `for (i = 0; i < max; i++)` + `if (key == OHCI_KEY_ST2 &&
i + 1 < max)` for the db[i+1] print. Fixed kernel: same prep, opuser
DUMPDMA (and root DUMPDMA, since the DF-2300 fix in the combined
patch also gates DUMPDMA - validated as root) prints exactly 8 rows,
no canary row, no ninth row. See run_fixed.log.

## Notes
* The disclosure surface is the console/dmesg (kernel-side print), not
  a copyout; severity Low as filed.
* The ST2 compounding read (db[i+1], fwohci.c:2379) can reach up to
  db[ndesc+1] when the last valid descriptor carries KEY_ST2 — the
  fix guards both.

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