β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4142

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4142 Β· 3 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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 to wi_rx_intr() (:614).
  • wi_rx_intr() reads the 60-byte struct wi_frame header 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 the wi_read_bap() return value.
  • wi_read_bap() (if_wi.c:1901-1916) fails on BAP seek timeout / OFF_ERR (wi_seek_bap :1868-1899 sets sc->sc_bap_off = WI_OFF_ERR and returns ETIMEDOUT/EIO), after which wi_rx_intr continues with an mbuf whose m_pkthdr.len claims 24 + len bytes (: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_sta JOIN_REQ writes a fixed-size wi_joinreq (:844-850); wi_info_intr LINK_STAT checks vap == NULL (:1500) before deref; COUNTERS loop is bounded by sizeof(sc->sc_stats)/4 (:1538) with min() β€” though ltbuf/stat are 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_intr checks its read (:1408).
  • (f) tx fid list: wi_tx_intr validates the alloc FID against sc_txd[cur].d_fid (:1463); partial wi_alloc_fid failure in wi_setup_locked (:664-674) aborts init before RUNNING is set, and wi_reset reinitializes 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_txrate rates[tp->ucastrate / 2] OOB (:1740-1742): ucastrate is validated by checkrate() 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_attach ratebuf/val uninit reads when RID len < 2 (:357, :436): device-fed, nrates capped (:438-439), no OOB; not filed.
  • wi_write_appie 40-byte cap checked (:2051). OK.
  • wi_read_nicid bounded reads (memset + fixed 8/12-byte buffers). OK.
  • DF-1400 (wi_write_wep LUCENT key overflow, :1751-1769) β€” known, excluded.

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_testable
baseline no→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-4142

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