# DF-1057 — FW_ASYREQ bcopy into &xfer->send.payload (field address) — VERDICT

## Verdict: INCONCLUSIVE (runtime) / CONFIRMED (source-level)

The bug is **confirmed real** by line-by-line source tracing — it is an
unambiguous `&` typo. Runtime reproduction is blocked by missing FireWire
hardware (no `/dev/fw0` on this guest).

## Mechanism (source-level trace)

1. **Code location:** `FW_ASYREQ` ioctl handler in
   `sys/bus/firewire/fwdev.c:521-588`.

2. **The bug** at `fwdev.c:560-561`:
   ```c
   if (pay_len > 0)
       bcopy((char *)fp + tinfo->hdr_len,
           (void *)&xfer->send.payload, pay_len);
   ```
   `&xfer->send.payload` takes the **address of the pointer field** within
   the `fw_xfer` struct. The correct form (used everywhere else in the
   driver) is `xfer->send.payload` (the VALUE = the kmalloc'd buffer address).

3. **Comparison with correct sites:**
   - `fwdev.c:558`: `bcopy(fp, (void *)&xfer->send.hdr, ...)` — CORRECT,
     `send.hdr` is an embedded struct (not a pointer).
   - `firewire.c:959`: `xfer->send.payload = kmalloc(send_len, ...)` — assigns
     the pointer.
   - `fwohci.c:918`: `&xfer->send.payload[0]` — correct dereference for DMA.
   - `firewire.c:1033`: `kfree(xfer->send.payload, ...)` — correct value use.
   Only `fwdev.c:561` erroneously takes the address of the pointer field.

4. **pay_len computation** (`fwdev.c:529-532`):
   ```c
   if ((tinfo->flag & FWTI_BLOCK_ASY) != 0)
       pay_len = MAX(0, asyreq->req.len - tinfo->hdr_len);
   xfer = fw_xfer_alloc_buf(M_FWXFER, pay_len, PAGE_SIZE);
   ```
   `asyreq->req.len` is `u16` (firewire.h:259), so pay_len ≤ ~65519.
   The allocation at `fw_xfer_alloc_buf` (firewire.c:958-959) correctly
   kmallocs `pay_len` bytes for `send.payload`.

5. **Struct layout** (`fw_xfer`, firewirereg.h:232-261):
   ```
   send.hdr (struct fw_pkt, ~16 bytes)
   send.payload (u_int32_t *, 8 bytes)  ← bcopy STARTS here
   send.pay_len (u_int16_t, 2 bytes)
   send.spd (u_int8_t, 1 byte)
   recv.hdr (~16 bytes)
   recv.payload (u_int32_t *, 8 bytes)  ← overwritten at pay_len >= 40
   recv.pay_len, recv.spd
   mbuf, link, malloc                   ← overwritten at pay_len > 56+
   [end of allocation]                  ← overflow into adjacent heap
   ```

6. **Three impacts:**
   - **(a) Heap overflow:** pay_len up to ~65519 bytes written into the xfer
     struct and past it into adjacent M_FWXFER heap. With grooming → local root.
   - **(b) Arbitrary kfree:** pay_len=8 overwrites only `send.payload`. Then
     `fw_xfer_free_buf()` at fwdev.c:586 → `kfree(xfer->send.payload, ...)`
     (firewire.c:1033) frees the attacker-chosen address.
   - **(c) Info leak:** corrupted `recv.payload` read by bcopy at fwdev.c:583.

## Exploit chain

**Local privilege escalation possible** — but requires `/dev/fw0` access
(operator group) AND a FireWire controller present. The overflow into
M_FWXFER heap can corrupt adjacent xfer objects or slab metadata. With
no SMAP/SMEP/KASLR on this guest, a hijacked function pointer in the
corrupted struct jumps to userspace shellcode calling
`commit_creds(prepare_kernel_cred(0))`.

**However**, the chain is blocked by the missing FireWire controller:
`/dev/fw0` doesn't exist. This is the **valid hard blocker** (Phase 6):
the vulnerable code path is unreachable without FireWire hardware.

## Why runtime reproduction is blocked

- No FireWire controller on this QEMU guest.
- `/dev/fw0` does not exist — `open("/dev/fw0")` returns ENOENT.
- The PoC correctly detects this and falls back to source-level output.
- FireWire IS in GENERIC (compiled in), but the driver only creates device
  nodes when a controller is detected at boot.

## PoC changes

Created `df-fw-asyreq-overflow.c` (from finding markdown) with graceful
fallback for missing `/dev/fw0`. Built and verified: compiles with
`-I/usr/src/sys`, runs, correctly reports no FW device and confirms the bug.

## Fix validation

The fix (`fix.diff`) removes the erroneous `&`:
```c
-   (void *)&xfer->send.payload, pay_len);
+   (void *)xfer->send.payload, pay_len);
```
Applied + compiled in combined-fix kernel (`#1`, rc=0, boots cleanly).
Runtime before/after not possible (no FireWire HW).

**fix_status: not_testable** (diff applies + compiles; runtime blocked by
missing HW — `/dev/fw0` does not exist).

## Recommended fix

Remove the `&` at fwdev.c:561. This matches the finding proposal exactly.
The fix is a single character change (`&` → removed) at the root cause.
