# DF-5547 VERDICT — fwmem_read_block() quadlet-rounded receive length overruns the destination buffer

## Reproduced: YES (controlled cross-chunk heap OOB write, production code)

`sys/bus/firewire/fwmem.c:197` — `fwmem_read_block()` sets
`xfer->recv.pay_len = roundup2(len, 4)` (via `fwmem_xfer_req(..., 0, roundup2(len, 4), ...)`)
while `xfer->recv.payload` (`fwmem.c:209`) is the caller's `len`-byte buffer
(`bp->b_data`, `fwmem_strategy()` `fwmem.c:357-368`, `iolen = MIN(bp->b_bcount, MAXLEN)`).
The receive sink `fw_rcv_copy()` (`sys/bus/firewire/firewire.c:1707-1722`) caps the copy at
`xfer->recv.pay_len`, NOT at the buffer length, so a block-read response padded to the
quadlet boundary (what FireWire responders do on the wire, and what a malicious node can
always send) writes `roundup2(len,4) - len` (1..3) responder-controlled bytes past the end
of the caller's buffer.

## How it was proven (production firewire.c + fwmem.c compiled into df5547.ko)

1. Production `fwmem_open()` on a synthetic `/dev/fwmem0.0` cdev (0660 root:operator
   equivalent — the node `fwdev_clone()` creates, `fwdev.c:827-849`).
2. A physio-shaped bio: `b_cmd = BUF_CMD_READ`, `b_bcount = 29` (odd), `b_data = C+3`
   where `C` is a groomed 32-byte heap chunk whose immediate neighbour `N = C+32` is
   guard-filled (`0xAA`).
3. Production `fwmem_strategy()` (`fwmem.c:325`) → `fwmem_read_block(len=29)` →
   `recv.pay_len = 32`, `recv.payload = C+3` → production `fw_asyreq()`/`fw_asystart()`
   (TX side driven by a stand-in of `fwohci_start_atq`/`fwohci_txd`).
4. An RRESB response with `data_length = 32` injected through production `fw_rcv()`
   (exactly what `fwohci_arcv()` does, `fwohci.c:2824-2835`).
5. Result (verbatim, run.log):

```
df5547: bcount=29 pay_len=32 ... | bytes past buffer: 41 42 43 aa -> OOB WRITE
df5547: N[0..3] now: 41 42 43 aa (was AA AA AA AA)
```

3 responder-chosen bytes landed in the ADJACENT heap chunk. All odd lengths
(1, 2, 3, 5, 2047) show the corresponding 3/2/1/3/1-byte OOB; lengths that are
multiples of 4 (28, 32) show no change (negative controls). Full RPC completion is
unaffected (`biodone=1, b_error=0, b_resid=0`), i.e. the request path works and the
overflow rides along silently.

## Impact ceiling (honest)

* The written bytes are fully chosen by the responder; the write is past the buffer the
  kernel handed the RPC, in the KLD harness into a neighbouring heap chunk (proven).
* On current DFly `physio()` (`sys/kern/kern_physio.c`), plain `read(fd, ubuf, odd)` uses
  a bounce buffer, so for USER reads the 1-3 extra bytes land in bounce-buffer slack and
  are discarded on copyout — the user-path manifestation is mostly benign today.
* Any `UIO_SYSSPACE`/kernel-originated read on `/dev/fwmem*`, or any future direct bio
  issuer of `fwmem_strategy()`, gets the full out-of-bounds write into adjacent kernel
  heap (exactly what the harness demonstrates). The length contract violation itself
  (`pay_len` > buffer) is unconditional and is the DF-5413 length-math class surfacing
  in the fwmem block-RPC assembly.
* Reachability: local operator-group user with a FireWire bus present doing an
  odd-length `read(2)` + any responder that pads (or a malicious node, unauthenticated);
  kernel callers as above.

## Fix validation

`fix.diff` (one line): advertise `len`, not `roundup2(len, 4)`, as the receive length.
Applied to the guest `/usr/src/sys/bus/firewire/fwmem.c`, module rebuilt from the patched
production file, identical trigger re-run (`fix_validation.log`):

| case | baseline | fixed |
|---|---|---|
| bcount 29 (cross-chunk groom) | `41 42 43 aa -> OOB WRITE` | `aa aa aa aa -> no change` |
| bcount 1/2/3/5/2047 | OOB WRITE | no change |
| bcount 28/32 (controls) | no change | no change |
| RPC completion | biodone=1, resid=0 | biodone=1, resid=0 |

**fix_status: fixed.**

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