# DF-2298 — Heap OOB read/write via zero-length segment array from integer overflow in `n`

**File:** `sys/bus/firewire/fwdma.c:145-166` + `sys/bus/firewire/fwdma.h:59-75`
**Verdict: REPRODUCED** — OOB read of a groom-controlled pointer + production
`fw_write` landing 2048 attacker-chosen bytes at the chosen kernel address.

## Claim

`FW_SSTBUF` (no validation, `fwdev.c:504-506`) → `fwdev_allocbuf`
(`fwdev.c:109`) computes `n = b->nchunk * b->npacket` as an **int multiply**.
For `nchunk = npacket = 0x10000` the product wraps to `0 mod 2^32`.
`fwdma_malloc_multiseg(fc, 4, 512, 0, ...)` then computes
`nseg = howmany(0, 4096/512) = 0`, kmallocs only the 32-byte
`struct fwdma_alloc_multi` header (`fwdma.c:162-163`) and **returns it as
success** with a zero-length `seg[]` array. `fwdev_allocbuf` stores the
*original* `nchunk`/`npacket` in the queue and marks `FWXFERQ_EXTBUF`, so
production `fw_write`/`fw_read` reach `fwdma_v_addr()` (`fwdma.h:59-66`),
which reads `am->seg[0].v_addr` — **8..24 bytes past the 32-byte
allocation** — and `uiomove()`s attacker iso data to that stale/groomed
pointer.

## How to build / run (no FireWire hardware needed — KLD harness)

The harness compiles the **unmodified production** `firewire.c + fwdma.c +
fwdev.c` into `df229x.ko` (DF-5613 pattern) and drives the real
`fw_ioctl` / `fw_write` paths. Test 2:

1. sprays the 32-byte kmalloc zone with chunks whose every qword is the
   victim pointer, then punches holes (every 2nd chunk freed);
2. `FW_SSTBUF{tx:{0x10000, 0x10000, 512}} + FW_STSTREAM` → the zero-seg
   `am` lands in a hole → the OOB `am->seg[0].v_addr` (at `am+40`) reads
   back **the pointer we chose**;
3. one production `fw_write()` (`fwdev.c:388-392`) uiomoves the 4-byte iso
   header + `len` (2044) payload bytes of attacker data to that address.

```sh
# guest, root, fresh `vm.sh reset with-src`
cd /root/df229x && sh build.sh && kldload ./df229x.ko
sysctl kern.df229x_test=2 && sysctl kern.df229x_status
```

### Expected (baseline, reproduced)
```
df2298/a1: am (32-byte kmalloc, nseg=0) -> OOB am->seg[0].v_addr = 0xfffff801187ea000
           (read at am+40); groom target = 0xfffff801187ea000
df2298/a1: fw_write err=0; victim page now: "" / "DF2298-OOB-WRITE"
df2298/a1: CONTROLLED WRITE CONFIRMED: attacker iso payload ... landed at the
           groom-chosen kernel address ... via the OOB-read seg[0].v_addr
kern.df229x_status: DF-2298 REPRODUCED: ... OOB-read-to-arbitrary-write
```
(Groom hit 4/4 attempts across two boots. Without the groom the same OOB
read hands `fw_write` an uncontrolled kernel pointer — a wild write/panic.)

### Expected (fix.diff applied, module rebuilt — fix_run.log)
```
df2298/a*: STSTREAM err=22 -- bufspec rejected, no zero-seg allocation
kern.df229x_status: DF-2298 nchunk=npacket=0x10000: STSTREAM err=22 (rejected, no write)
kern.df229x_status: DF-2298b npacket=0: STSTREAM err=22      (trivial n=0 variant)
kern.df229x_status: regression OK: legit bufspec still works
```

## Files
df229x.c (harness; test 2 = this finding, test 4 = trivial `npacket=0`
variant), Makefile + shim/, build.sh, run_bootA.sh / run_fix.sh,
run.log (decisive baseline), fix_run.log, baseline_build.log /
fix_build.log, env.txt, fix.diff, VERDICT.md, manifest.json, verdict.json
