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

wpi_rx_done reads tail and sets m_len from unbounded firmware head->len (OOB read / heap info-leak)

Summary

wpi_rx_done at if_wpi.c:1967-1970: head->len (uint16 from firmware) used without upper bound. tail=(head+1)+len at :1969, tail->flags read at :1970 BEFORE lower-bound check at :1984. RX mbuf is MJUMPAGESIZE=4096 but desc+stat+stat->len+head+len+tail can exceed 4096 for len>~4036. m_len set to unbounded len at :2028 -> ieee80211_input walks past cluster. Same class as DF-1123 (iwn). Unauthenticated 802.11 attacker or firmware inconsistency. Fix: check len against remaining cluster space before tail deref.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1157 Β· 8 files
FileTypeDescriptionSize
fix.diff suggested-fix git-apply-able: upper-bound head->len against the RX cluster before tail is computed/derefed 1.1 KB view raw
build.sh build-script documents the combined nativekernel fix-build 696 B view raw
run.sh run-script documents the runtime-unreachable status 1.3 KB view raw
fix_validation.txt build-log apply+compile+boot evidence for the combined single-fix kernel (if_wpi.o, -Werror, #1) 1.8 KB view raw
fix_build.log build-log nativekernel build log tail (NK_DONE rc=0) 68.9 KB view raw
env.txt environment guest uname/cc, no WiFi hardware, wpi in GENERIC 1.3 KB view raw
VERDICT.md verdict full narrative: mechanism, reachability, uid0 assessment, fix validation 4.9 KB ↓ raw
README.md readme status + how to reproduce (source-level) 1.4 KB ↓ raw
README.md readme status + how to reproduce (source-level)
↓ download raw

DF-1157 β€” PoC evidence pack

wpi_rx_done reads tail and sets m_len from unbounded firmware head->len (OOB heap read / oversized mbuf) β€” sys/dev/netif/wpi/if_wpi.c:1968-2028.

head->len (firmware uint16_t, no upper bound) is used to compute tail = (head+1)+len and dereference tail->flags at :1970 before the lower-bound check at :1984. The RX buffer is MJUMPAGESIZE=4096, so a large len makes tail point past the cluster (OOB heap read), and m_len=len hands an oversized mbuf to ieee80211_input. wpi twin of DF-1123 (iwn).

Status

Source-confirmed. NOT reproduced at runtime on this guest: wpi IS compiled into X86_64_GENERIC:272, but the RX handler only runs on an Intel 3945ABG adapter, and the guest has no WiFi hardware. No standalone harness (the path is not syscall-reachable).

Reproduce (source-level + fix)

./build.sh          # documents the fix-validation (combined nativekernel build)
./run.sh            # documents the runtime-unreachable status

See VERDICT.md for the source-level confirmation.

Fix

fix.diff upper-bounds head->len against the RX cluster right after it is read, before tail is computed/derefed. Validated: applies + compiles clean in a combined single-fix kernel (if_wpi.o, -Werror); boots #1; the bound check is unconditional. See VERDICT.md / fix_validation.txt.

VERDICT.md verdict full narrative: mechanism, reachability, uid0 assessment, fix validation
↓ download raw

DF-1157 β€” wpi_rx_done reads tail and sets m_len from unbounded firmware head->len (OOB heap read / oversized mbuf)

Verdict

Source-confirmed; NOT reproduced at runtime on this guest (no Intel 3945ABG wpi adapter). The bug is a genuine unbounded-firmware-length OOB read + oversized-mbuf-walk, confirmed by line-by-line source trace. It is the wpi twin of DF-1123 (iwn). wpi is compiled into X86_64_GENERIC:272, so the code path exists in the live kernel image, but the RX interrupt handler is only registered when a wpi adapter attaches β€” and this QEMU/KVM guest has no WiFi hardware (pciconf shows no 0x028000 device). This is the valid hard blocker: runtime-unreachable on this guest (no matching hardware) β€” a latent HW-gated read. The fix was validated to apply + compile + boot clean.

Mechanism (trigger β†’ primitive β†’ effect)

In wpi_rx_done() (sys/dev/netif/wpi/if_wpi.c), the RX frame is received into data->m, a MJUMPAGESIZE (= PAGE_SIZE = 4096) jumbo cluster (if_wpi.c:1069,1990; sys/param.h:473):

   if_wpi.c:1961  if (stat->len > WPI_STAT_MAXLEN) goto fail1;   // stat bounded to 20
   if_wpi.c:1967  head = (struct wpi_rx_head *)((caddr_t)(stat+1) + stat->len);
   if_wpi.c:1968  len  = le16toh(head->len);          // firmware u16, NO upper bound
   if_wpi.c:1969  tail = (struct wpi_rx_tail *)((caddr_t)(head+1) + len);
   if_wpi.c:1970  flags = le32toh(tail->flags);        // <-- DEREF tail BEFORE any bound
   ...
   if_wpi.c:1984  if (len < sizeof(struct ieee80211_frame_ack)) ...   // LOWER bound, AFTER tail deref
   ...
   if_wpi.c:2028  m->m_pkthdr.len = m->m_len = len;    // unbounded -> ieee80211_input walk
  • head->len is a firmware-controlled uint16_t (0..65535) with no upper bound. The space from head+1 to the end of the 4096-byte cluster is ~4036 bytes (after stat->len ≀ 20), so any len > ~4036 makes tail point past the 4KB cluster.
  • flags = le32toh(tail->flags) at :1970 dereferences that OOB tail before the lower-bound check at :1984 β†’ OOB heap read / info leak (or fault).
  • m_len = len at :2028 then hands ieee80211_input an mbuf claiming an oversized body β†’ walks past the cluster.

Threat model / reachability

  • Attacker: a buggy or hostile wpi firmware/PHY, or a malicious 802.11 AP (firmware inconsistency). AV:A/AC:H per the CVSS. Reached on the wpi RX interrupt path.
  • On this guest: NOT reachable β€” no wpi adapter, so the handler never registers. wpi is in GENERIC so the vulnerable code ships in the default kernel, but it is inert without the hardware.

Exploit chain

None β€” valid hard blocker (HW-gated). No unprivileged-guest syscall drives the wpi RX path; the only inputs are a firmware/PHY inconsistency or a crafted RF frame, neither of which is present on the guest.

PoC changes

Authored from scratch. Deliverables: fix.diff (upper-bound head->len against the cluster before the tail deref), build.sh, run.sh (document the runtime-unreachable status), VERDICT.md, manifest.json, env.txt, fix_validation.txt, fix_build.log.

fix.diff inserts, right after len = le16toh(head->len) (if_wpi.c:1968) and before computing/dereferencing tail, an upper bound against the RX cluster:

if ((caddr_t)(head + 1) + len + sizeof(struct wpi_rx_tail) >
    mtod(data->m, caddr_t) + MJUMPAGESIZE) {
    DPRINTF(sc, WPI_DEBUG_RECV, "%s: frame too long: %d\n", __func__, len);
    goto fail1;
}

This matches the iwn (DF-1123) fix shape. With the guard, (head+1)+len+tail can never exceed the 4KB cluster, so the tail deref and the oversized m_len are both eliminated.

Fix validation (Phase 8)

  • fix.diff applies cleanly (git apply --check OK; Hunk #1 @1966).
  • Combined single-fix kernel build (DF-1157 + DF-1172 fixes together, warm obj): make -j6 nativekernel KERNCONF=X86_64_GENERIC β†’ NK_DONE rc=0; if_wpi.o compiled clean under -Werror; booted #1: Fri Jul 17 06:10:36 UTC 2026.
  • The bound check is unconditional (not under WPI_DEBUG); the DPRINTF message string is absent from the binary only because DPRINTF is a no-op unless WPI_DEBUG is defined (if_wpi_debug.h:25,141).
  • fix_status: not_testable for runtime: no wpi adapter on the guest to drive the RX path. Validated at apply + compile + boot level.

Kernel references (confirmed)

Fix verification

not_testable

compile+harness validated

see evidence pack

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. wpi_rx_done head->len no upper bound -> tail OOB deref + m_len OOB. wpi in GENERIC but no WiFi HW.