# DF-2296 — FW_ASYREQ unbounded pay_len bcopy over-read (+ discovered sibling write bug)

## Verdict (short)
REPRODUCED on the stock `fwdev.o`/`firewire.o` binaries — core claim
confirmed; **plus a worse sibling bug was discovered on the same line**
(see Sibling finding below — flagged for a new DF-ID by the
orchestrator).

Claim proven:
* `pay_len = MAX(0, asyreq->req.len - tinfo->hdr_len)` (fwdev.c:530) is
  never bounded by `sizeof(asyreq->data)` (2048 bytes). With req.len
  4096 (u16 max 65535), the bcopy at fwdev.c:560 copies 4080 bytes from
  `asyreq+36` — 2032 bytes past the 2084-byte M_IOCTLOPS staging buffer
  (sys_generic.c:675) — into the xfer payload area.
* **S400 ordering proven**: with fc->maxrec=10 (fwohci.c:521:
  `fc->maxrec = fc->speed + 8`, S400 speed 2 → MAXREC 2048 — note: the
  original claim said 512; actual stock S400 gate is 2048 quadlet
  bytes), `fw_asyreq` rejects with `send.pay_len > maxrec`
  (firewire.c:210) and the ioctl returns EINVAL — **but the bcopy at
  fwdev.c:560 runs unconditionally BEFORE fw_asyreq** (fwdev.c:565),
  dmesg confirms the gate line after the copy executed.
* **Full path on big-maxrec**: with fc->maxrec=11 (S800 speed 3 →
  MAXREC 4096), the same request completes the entire stock path —
  fw_asyreq → fw_asystart → the interposed OHCI TX start records a 4080-
  byte "wire" payload snapshot. On real hardware those bytes (including
  the 2032 bytes read past the ioctl buffer) are DMA'd onto the bus.
  fwohci max maxrec is 11 (speed+8, MAX_SPEED 3) → wire disclosure up
  to 2048 bytes of adjacent kernel heap on S800-capable controllers.

Fix validated: `pay_len > sizeof(asyreq->data)` → EINVAL (before any
alloc/copy); legit req.len=2064 (pay_len 2048 == capacity) still runs
the full path identically (rc=0, wire snapshot 2048 bytes).

## Sibling finding discovered during verification (NEW — needs an ID)
`fwdev.c:561`: `bcopy(..., (void *)&xfer->send.payload, pay_len);` —
copies into the ADDRESS OF the payload pointer field, not the payload
buffer (FreeBSD's original is `(void *)xfer->send.payload`). For ANY
FW_ASYREQ block request this smashes the tail of `struct fw_xfer`
(send.payload/pay_len/spd, recv.hdr/payload/pay_len, mbuf, link,
malloc — offsets published by the rig: send.payload=+80 … malloc=+144,
sizeof=152) and **overruns the 160-class fw_xfer allocation by up to
~63KB with fully user-controlled packet bytes** (a kernel heap
overwrite primitive reachable by the same operator-group ioctl, no
peer needed). Three distinct panics were captured while walking this
path stock (GPF in `_kfree` freeing the smashed send/recv.payload,
page fault in `pmap_kvtom`), and behavioral control of the smashed
fields was demonstrated (planted recv.hdr tcode steered the response
path; planted lenval steered the MAXREC gate). One-character fix:
drop the `&`. See smash_panic_gfp_kfree.txt / dmesg_phaseA_gate.txt /
VERDICT.md.

## Files
* poc_2296.c — trigger with stock-compatible field planting (keeps the
  smashed fw_xfer tail valid so the full stock path completes)
* run_2296.sh + run_2296.log — A (S400 reject-after-copy), B (S800 full
  path), C (response-path write variant) + spray canary analysis
* dmesg_phaseA_gate.txt, dmesg_serial_run.txt, smash_panic_gfp_kfree.txt
* spraydump.{A1,A2,B1,B2,C}.txt — canary dumps
* run.legit_baseline.log, fix_run.log — A/B evidence
* fix.diff — combined fix (this finding: pay_len bound)
