Unchecked M_PREPEND(M_NOWAIT) failure in fwe_as_output() NULL-derefs the already-freed mbuf β guaranteed kernel panic under mbuf pressure
Summary
M_PREPEND frees the chain and returns NULL on m_gethdr(M_NOWAIT) failure (mbuf.h:465-482, uipc_mbuf.c:1500-1521); fwe_as_output then reads m->m_pkthdr.len unconditionally -> NULL-offset fault panic. Any egress frame with <2 bytes leading space (cluster-filled/forwarded/VLAN) takes the alloc path; unpriv local user + mbuf starvation spray reaches it on an up fwe0. Pure DoS read fault. NULL-check+oerror+return-xfer fix authored. FireWire-hw-gated.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4763 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.3 KB | β raw | |
| VERDICT.md | β | 3.6 KB | β raw | |
| fwe_trigger.c | β | 2.7 KB | view raw | |
| build.sh | β | 89 B | view raw | |
| run.sh | β | 149 B | view raw | |
| build.log | β | 139 B | view raw | |
| run.log | β | 847 B | view raw | |
| env.txt | β | 904 B | view raw | |
| fix.diff | β | 1.2 KB | view raw | |
| verdict.json | β | 2.7 KB | view raw | |
| manifest.json | β | 748 B | view raw |
DF-4763 β unchecked M_PREPEND(M_NOWAIT) failure in fwe_as_output() NULL-derefs the freed mbuf β guaranteed kernel panic
File: sys/dev/netif/fwe/if_fwe.c:506-511 (pass 2).
Build / Run / Expected
build.sh # cc -O -o fwe_trigger fwe_trigger.c (on the audit guest) run.sh # unprivileged UDP flood toward fwe0
On hardware with an OHCI-1394 controller (fwe0 up, configured by the
admin), flood the interface while exhausting mbufs
(e.g. sysctl -w kern.ipc.nmbclusters=... pressure or plain socket
spray) β occasionally the TX mbuf has < 2 bytes leading space, so
M_PREPEND(m, ETHER_ALIGN, M_NOWAIT) must allocate; on failure the
macro frees the chain and sets m = NULL (sys/sys/mbuf.h:465-482),
and fwe_as_output unconditionally dereferences
m->m_pkthdr.len (if_fwe.c:509) β NULL+offset read β kernel panic.
Expected: immediate Fatal kernel mode fault ⦠fault VA 0x⦠/
panic: lp254 ... style page fault in fwe_as_output. DoS only β no
write primitive (the panic is a read of ~NULL+offset; the packet path
stops there).
On the audit QEMU guest there is no FireWire controller (QEMU has no
OHCI-1394 device model), so fwe0 cannot exist β see run.log (exit 2)
and env.txt. Runtime verification is therefore blocked by absent
hardware; the code path itself is unambiguous (VERDICT.md).
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):
/* 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)
- Peer traffic or local flood:
./fwe_trigger fwe0while spraying mbufs (e.g. thousands of AF_UNIX/SO_RCVBUF sockets or a second flood) to starve m_gethdr. - Baseline: expect panic in fwe_as_output within minutes.
- 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.
Fix verification
not_testablefix.diff authored against read-only sys/; cannot validate on the guest (fwe driver cannot attach without OHCI-1394 hardware).
fix.diff in this pack
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (path:line proof incl. M_PREPEND contract); run.log (PoC builds BUILD_OK on guest, exits 2 'fwe0 not present' as root and unprivileged); fix.diff (NULL check returning xfer to the free list)
PoC changes
Authored from scratch (no seed); if_nametoindex probe + SO_BROADCAST UDP flood; built and ran first try on guest (BUILD_OK, exit 2 hardware-absent).
Verified recommended fix
Check m == NULL after M_PREPEND: count oerror, STAILQ_INSERT_TAIL the xfer back onto fwe->xferlist, and continue (see fix.diff).
Verdict
NOT runtime-testable on the audit guest: fwe(4) requires an OHCI-1394 FireWire controller and the QEMU guest has none (env.txt / run.log: if_nametoindex('fwe0') -> 'Device not configured'); QEMU has no 1394 device model - contract status not_testable, mapped to 'inconclusive' for the poc_results enum. The bug itself is unambiguous straight-line code: M_PREPEND(m, ETHER_ALIGN, M_NOWAIT) at if_fwe.c:506 frees the mbuf chain and returns NULL on allocation failure (sys/sys/mbuf.h:465-482, sys/kern/uipc_mbuf.c:1500-1521), and fwe_as_output() unconditionally dereferences m->m_pkthdr.len at if_fwe.c:509 and 511 -> NULL+offset read -> guaranteed kernel panic. Reachable by any unprivileged local user sending traffic on an admin-configured fwe0 under mbuf starvation; pure DoS, no write primitive.
No comments yet.