# DF-4763 VERDICT — M_PREPEND NULL dereference in fwe_as_output()

## Status: NOT RUNTIME-TESTABLE on the audit guest (hardware absent)

Same blocker as DF-4762: the QEMU guest has no OHCI-1394 controller
(env.txt), QEMU has no 1394 device model, so `fwe0` can never attach
(DRIVER_MODULE(fwe, firewire, ...), if_fwe.c:619). The trigger builds
and runs on the guest and exits 2 "fwe0 not present" (run.log) — the
PoC is sound, only the device is missing. Contract status `not_testable`,
persisted as `inconclusive` for the poc_results enum.

## The bug (source-level, unambiguous)

`fwe_as_output()` (if_fwe.c:483):

```c
	/* keep ip packet alignment for alpha */
	M_PREPEND(m, ETHER_ALIGN, M_NOWAIT);      /* if_fwe.c:506 */
	fp = &xfer->send.hdr;
	*(u_int32_t *)&xfer->send.hdr = *(int32_t *)&fwe->pkt_hdr;
	fp->mode.stream.len = m->m_pkthdr.len;    /* if_fwe.c:509  <-- NULL deref */
	xfer->mbuf = m;
	xfer->send.pay_len = m->m_pkthdr.len;     /* if_fwe.c:511  <-- NULL deref */
```

DragonFly's M_PREPEND contract (sys/sys/mbuf.h:464-483):

> "If how is M_NOWAIT and allocation fails, the original mbuf chain is
> freed and m is set to NULL."

m_prepend() (sys/kern/uipc_mbuf.c:1500-1521) confirms: m_gethdr(how) ==
NULL → `m_freem(m); return (NULL)`. The macro's fast path
(`M_LEADINGSPACE(m) >= 2`) needs no allocation, but egress mbufs whose
first mbuf fills the cluster to its start (e.g. cluster-backed frames
with zero leading room — large UDP datagrams, forwarded paths, VLAN
popped headers) take the allocation path. Under mbuf starvation
(attacker-controllable with unprivileged socket spray against a system
with default nmbufs/nmbclusters) `m_gethdr(M_NOWAIT)` fails and `m`
becomes NULL — and is already freed. The unconditional `m->m_pkthdr.len`
at 509 reads at ~NULL+offset (offsetof(struct mbuf, m_pkthdr.len)) →
kernel page fault → panic.

Reachability: any local user can emit packets on `fwe0` once the admin
has configured/raised it (sending needs no privilege). The panic
requires the allocation-failure branch, i.e. memory pressure + a
cluster-head packet: probabilistic under flood, not attacker-forced
deterministically. Impact = reliable *eventual* kernel panic (DoS);
no write primitive, nothing beyond the faulting read.

Class comparison: same unchecked-M_PREPEND family as other drivers
fixed over the years; lnc DF-4746-style TX-path care. Severity Medium
(local kernel DoS, unusual config: FireWire NIC present and up).

## Why not false-positive

The deref sequence is straight-line code with no intervening NULL
check; the failure mode of M_PREPEND(M_NOWAIT) is documented in the
header and implemented in m_prepend(). There is no other early return
between 506 and 509.

## Fix validation

Not possible on this guest (no hardware): fix_status = not_testable.
`fix.diff` adds the NULL check, returns the xfer to the free list and
counts an oerror (matches how sibling drivers handle it).

## Validation procedure on real hardware (for the record)

1. Peer traffic or local flood: `./fwe_trigger fwe0` while spraying
   mbufs (e.g. thousands of AF_UNIX/SO_RCVBUF sockets or a second
   flood) to starve m_gethdr.
2. Baseline: expect panic in fwe_as_output within minutes.
3. Patched (fix.diff applied, kernel rebuilt): no panic, oerrors
   counter increments instead.

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