# DF-5414 — `fw_read` frees a queued xfer without dequeuing it (dequeue is `#if 0`) → use-after-free + double free

**File:** `sys/bus/firewire/fwdev.c` · **Severity:** High · **Bucket:** memcorrupt · **Confidence:** certain
**Status: reproduced on the QEMU guest** (production `firewire.c`+`fwdma.c`+
`fwdev.c` in KLD `df5413.ko`, DF-5302/DF-5321 evidence class).

## Root cause (production path:line)

The FWACT_CH receive path (`firewire.c:1874-1901`) takes the bind's xfer off
`bind->xferlist` and appends it to `fc->ir[sub]->q` (`firewire.c:1893-1896`),
then wakes the reader.  `fw_read()` takes the first xfer
(`fwdev.c:281`) and enters the per-packet branch (`fwdev.c:304`) — where the
**dequeue code is inside `#if 0 /* XXX broken */` (`fwdev.c:305-317`) but
`fw_xfer_free(xfer)` (`fwdev.c:318`) is not**:

```c
} else if(xfer != NULL) {
#if 0 /* XXX broken */
	/* per packet mode or FWACT_CH bind?*/
	crit_enter();
	ir->queued --;
	STAILQ_REMOVE_HEAD(&ir->q, link);
	crit_exit();
	...
#endif
	fw_xfer_free( xfer);          /* <-- frees, never unlinks */
}
```

Read #1 frees the xfer while it stays linked in `ir->q` (and `ir->queued`
never decrements).  Read #2 fetches the same (dangling) head and frees it
**again** → use-after-free read of `xfer->state/…/xfer->malloc` inside
`fw_xfer_unload()` (`firewire.c:995-1020`) and a **double `kfree`**
(`firewire.c:1049`).  Every subsequent read frees it yet again.

Trigger: any asynchronous packet that lands in a `FW_SBINDADDR`-bound address
range — an **unauthenticated remote FireWire peer** (the bus has no
authentication) — followed by local `read(2)`s on the open `/dev/fwN.M`.
`fwmem.c`'s kernel-side fwmem traffic targets the same machinery.

## Demonstration (guest, test 2)

The harness queues exactly one xfer the way the production FWACT_CH path
does (`firewire.c:1883-1897`), then calls the production `fw_read` twice:

```
df5414: injected xfer=0xfffff801176997c0, ir->q head=0xfffff801176997c0 queued=1
df5414: read#1 err=0 -- ir->q head=0xfffff801176997c0 (STILL THE FREED XFER) queued=1
df5414: realloc p1=0xfffff801176997c0 (same address as freed xfer)
df5414: read#2 err=0 -- p2=0xfffff801176997c0 p1=0xfffff801176997c0 -> DUPLICATE ALLOCATION (double free proven)
```

After read#1 the queue head is the freed pointer (dangling, provable without
allocator assumptions); read#2 unconditionally frees it again; the allocator
then hands the same chunk to two owners (`p1 == p2 == xfer`) — the classic
double-free consequence and the takeover primitive (overlap two live kernel
objects).

## What to run

```
./build.sh
sh run.sh      # kern.df5413_test=2
```

## Fix

`fix.diff` (shared) un-`#if-0`s the dequeue (crit + `queued--` +
`STAILQ_REMOVE_HEAD`) before the free.  Validated: with the fix, read#1
leaves `ir->q head=0` and `queued=0`, read#2 sleeps and errors, and no
duplicate allocation occurs (`fix_run.log`).
