wi_rx_intr ignores wi_read_bap() failure: uninitialized kernel heap delivered to the 802.11 stack and BPF (device-fed info disclosure)
Summary
wi_rx_intr reads the firmware frame header with a checked wi_read_bap but copies the frame body with an UNCHECKED one; on BAP failure (possibly after a partial transfer) the mbuf tail is uninitialized heap, yet m_pkthdr.len is set to the full length and the frame is delivered to ieee80211_input and BPF. Length handling itself is correctly bounded - an uninit-read disclosure, not an overflow. Device-fed threat model: malicious/faulty Hermes/Prism2 firmware exposes stale kernel heap (prior packet data, pointers, key material) in received frames. NOTE: the prompt's other hunt areas (wi_read_record/wi_in_scan/legacy ioctls/monitor prepend) do NOT exist in DragonFly's wi - removed in the net80211 conversion. Check-and-drop fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4142 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.3 KB | β raw | |
| VERDICT.md | β | 5.2 KB | β raw | |
| fix.diff | β | 880 B | view raw |
DF-4142 β wi_rx_intr ignores wi_read_bap() failure: uninitialized kernel heap delivered to the network stack / BPF
File: sys/dev/netif/wi/if_wi.c (Prism2/2.5/3 + Lucent Hermes driver)
What
wi_rx_intr() (if_wi.c:1277) reads the firmware frame header with a
checked wi_read_bap() (:1293), then allocates an mbuf sized for the
firmware-supplied wi_dat_len (:1319-1343) and copies the frame body with
an unchecked one:
if_wi.c:1356
wi_read_bap(sc, fid, sizeof(frmhdr),
m->m_data + sizeof(struct ieee80211_frame), len);
m->m_pkthdr.len = m->m_len = sizeof(struct ieee80211_frame) + len;
wi_read_bap() (if_wi.c:1901) returns an error when wi_seek_bap() times
out or the BAP reports WI_OFF_ERR (:1880-1895) β and, importantly, the
CSR_READ_MULTI_STREAM_2 at :1913 may have transferred only part of cnt
words before the failure is noticed. On any such failure the mbuf tail
(len bytes minus whatever was actually read) is uninitialized kernel
heap, yet m_pkthdr.len is set to the full
sizeof(struct ieee80211_frame) + len and the frame is handed to
ieee80211_input() (:1390-1395) and to any BPF listeners via the normal
receive path.
Threat model (device-fed, per the audit charter): a malicious or faulty Hermes/Prism firmware/PCIe device (the NIC DMA-feeds the FID and the BAP state) that makes a mid-frame BAP read fail leaks stale kernel heap memory (possible previously-freed packet data, key material, pointers) into frames observable by root/operator via BPF and partially forwarded to the network.
Bounds note (this is NOT a length bug): len is correctly bounded β
off + len > MCLBYTES drops the frame (or zeroes len in monitor mode)
at :1326-1338, and the mbuf is cluster-backed when off + len > MHLEN;
the write stays within the mbuf. The defect is solely the ignored return
value β uninit-read info leak.
Fix (fix.diff, applies clean, never applied to sys/)
Check the return, count the error, and drop the frame (mirroring the adjacent error paths at :1293-1302 / :1344-1353).
PoC
Not runnable on this guest: wi(4) requires PCMCIA/ISA/PCI Prism or Lucent
hardware; the QEMU guest has none (pciconf shows 0 wireless devices), and
the trigger additionally requires a device that fails a mid-frame BAP
transfer. Source-level proof only; see VERDICT.md.
DF-4142 VERDICT β wi_rx_intr: ignored wi_read_bap() return β uninitialized heap in delivered frames
Status: inconclusive (hardware-gated; no wi(4) NIC on the guest). Defect existence certain at source level; no kernel-write primitive; ceiling is a device-triggered kernel-heap info disclosure into received frames.
Trace
wi_intr()(if_wi.c:594) takes WI_LOCK and dispatches RX events towi_rx_intr()(:614).wi_rx_intr()reads the 60-bytestruct wi_frameheader from the NIC FID with a checked read (:1293-1302 β error β ACK, count, return).len = le16toh(frmhdr.wi_dat_len)(:1319) β firmware-fed u16; correctly bounded against MCLBYTES/MHLEN (:1326-1343); monitor-mode oversized frames are truncated to len=0 (:1337). No length bug.- mbuf is allocated (
m_getcl/m_gethdr, :1340-1343), the 24-byte 802.11 header is memcpy'd from the (already-verified)frmhdr.wi_whdr(:1354-1355), then the body copy at :1356 ignores thewi_read_bap()return value. wi_read_bap()(if_wi.c:1901-1916) fails on BAP seek timeout / OFF_ERR (wi_seek_bap:1868-1899 setssc->sc_bap_off = WI_OFF_ERRand returns ETIMEDOUT/EIO), after whichwi_rx_intrcontinues with an mbuf whosem_pkthdr.lenclaims24 + lenbytes (:1358) while the tail was never (or only partially) transferred.- The mbuf is then delivered to
ieee80211_input()(:1390-1395); BPF taps see it through the normal 802.11 receive path; monitor-mode vaps pass the raw frame up unchanged.
Uninitialized bytes come from freshly allocated (unzeroed) mbuf clusters or mbuf clusters recycled from prior traffic β the classic stale-kernel-heap disclosure shape.
Why inconclusive, not not_reproduced
The defect cannot be triggered on this guest: no Prism/Hermes hardware is
present (pciconf -l | grep -ci 'atheros|prism|β¦' = 0; no wi interface),
and triggering requires a device that faults a BAP transfer mid-frame.
There is nothing to "fail to reproduce" β the code path is unconditional
whenever the device misbehaves; the C-level defect (ignored return) is
directly visible at :1356 with every adjacent error path (:1293, :1344)
doing the check properly.
Pass-2 areas hunted in if_wi.c β results (negative result weight)
Prompt-directed areas, with dispositions:
- (a) wi_read_record/wi_write_record RID walks: do not exist in DragonFly's wi driver β the legacy wi_ioctl/SIOC[SG]PRISMSTATS record machinery was removed in the net80211 conversion (rg over sys/dev/netif/wi/: zero matches for wi_read_record|wi_write_record| wi_ioctl|wi_getdbg). Dead surface.
- (b) wi_in_scan Prism2 scan-RID walks: do not exist (same removal).
- (c) 802.11 compat ioctl conversions: driver installs no if_ioctl handler; SIOCS80211 is handled by net80211. MTU clamped to IEEE80211_MTU_MAX=2290 (netproto/802_11/wlan/ieee80211_ioctl.c:3481-3487) and SIOCSIFMTU requires SYSCAP_RESTRICTEDROOT (sys/net/if.c:2261).
- (d) join/assoc result handling:
wi_newstate_staJOIN_REQ writes a fixed-sizewi_joinreq(:844-850);wi_info_intrLINK_STAT checksvap == NULL(:1500) before deref; COUNTERS loop is bounded bysizeof(sc->sc_stats)/4(:1538) withmin()β thoughltbuf/statare uninit-read candidates if the device returns garbage (device-fed, control-flow only, no memory unsafety β noted, not filed). - (e) rx frame header parse: bounded (see above); the ignored BAP return is
this finding.
wi_tx_ex_intrchecks its read (:1408). - (f) tx fid list:
wi_tx_intrvalidates the alloc FID againstsc_txd[cur].d_fid(:1463); partialwi_alloc_fidfailure inwi_setup_locked(:664-674) aborts init before RUNNING is set, andwi_resetreinitializes the NIC β no kernel memory issue. - (g) monitor header prepend paths: DragonFly's copy has no prism2 monitor header prepend (radiotap fields filled from frmhdr only, :1364-1380).
Additional candidates killed:
wi_write_txraterates[tp->ucastrate / 2]OOB (:1740-1742): ucastrate is validated bycheckrate()against the 11b rateset with 0x7f mask (ieee80211_ioctl.c:2180-2190, :2231-2235) β index β€ 11 of 12. Killed.wi_mwrite_bapβm_copydata(m, 0, totlen, sc->sc_txbuf[2304])(:1950): MTU β€ 2290 + LLC 8 + WEP 8 β stripped 24-byte header = 2282 β€ 2304 (driver is WEP-only: ic_cryptocaps=WEP, if_wi.c:431). Killed.wi_attachratebuf/valuninit reads when RID len < 2 (:357, :436): device-fed, nrates capped (:438-439), no OOB; not filed.wi_write_appie40-byte cap checked (:2051). OK.wi_read_nicidbounded reads (memset + fixed 8/12-byte buffers). OK.- DF-1400 (wi_write_wep LUCENT key overflow, :1751-1769) β known, excluded.
Recommended fix
fix.diff (in this pack): check the wi_read_bap() return at :1356; on
failure free the mbuf, ACK WI_EV_RX, bump ic_ierrors, and return β exactly
the pattern of the two adjacent error paths.
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 applies cleanly to sys/ (git apply --check) and mirrors the two adjacent checked-error paths, but cannot be executed: no wi(4) hardware on the guest to exercise wi_rx_intr, so no baseline/patched kernel run is possible. Harness-level validation is not meaningful here because the defect is an ignored return value inside a device ISR path.
fix.diff; VERDICT.md trace
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (line-by-line trace + full pass-2 disposition of prompt areas (a)-(g) and killed candidates); fix.diff (git apply --check clean, not applied)
PoC changes
n/a β no runnable PoC possible without Prism2/Hermes hardware; pack is analysis + fix only
Verified recommended fix
Check the wi_read_bap() return at if_wi.c:1356; on failure m_freem, ACK WI_EV_RX, ic_ierrors++, return (mirrors :1293/:1344 error paths)
Verdict
Defect certain at source level: wi_rx_intr() copies the RX frame body with wi_read_bap() at if_wi.c:1356 while ignoring its return value, then sets m_pkthdr.len to the full firmware-supplied length (:1358) and delivers the mbuf to ieee80211_input()/BPF (:1390-1395). On any BAP failure (wi_seek_bap timeout or WI_OFF_ERR, if_wi.c:1880-1895) the mbuf tail is uninitialized kernel heap (m_getcl/m_gethdr do not zero). Frame length handling itself is correctly bounded (MCLBYTES/MHLEN checks at :1326-1343), so this is an uninit-read disclosure, not an overflow. Cannot manifest on this guest: no wi(4) hardware present (pciconf: 0 wireless devices) and the trigger needs a device that faults a mid-frame BAP transfer. Ceiling: device-fed kernel-heap info leak into received frames (BPF/forwarded traffic).
No comments yet.