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)
PoC verification
Evidence pack
findings/poc/DF-1157 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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.
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->lenis a firmware-controlleduint16_t(0..65535) with no upper bound. The space fromhead+1to the end of the 4096-byte cluster is ~4036 bytes (afterstat->len β€ 20), so anylen > ~4036makestailpoint past the 4KB cluster.flags = le32toh(tail->flags)at:1970dereferences that OOBtailbefore the lower-bound check at:1984β OOB heap read / info leak (or fault).m_len = lenat:2028then handsieee80211_inputan 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.
Recommended fix
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.diffapplies cleanly (git apply --checkOK; 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.ocompiled clean under-Werror; booted#1: Fri Jul 17 06:10:36 UTC 2026. - The bound check is unconditional (not under
WPI_DEBUG); theDPRINTFmessage string is absent from the binary only becauseDPRINTFis a no-op unlessWPI_DEBUGis defined (if_wpi_debug.h:25,141). fix_status: not_testablefor runtime: no wpi adapter on the guest to drive the RX path. Validated at apply + compile + boot level.
Kernel references (confirmed)
sys/dev/netif/wpi/if_wpi.c:1968βlen = le16toh(head->len)(unbounded)sys/dev/netif/wpi/if_wpi.c:1969-1970β tail computed/derefed before bound checksys/dev/netif/wpi/if_wpi.c:1984β lower-bound check (too late)sys/dev/netif/wpi/if_wpi.c:2028βm_len = len(oversized mbuf walk)sys/dev/netif/wpi/if_wpi.c:1069,1990β RX buffer =MJUMPAGESIZEsys/param.h:473βMJUMPAGESIZE = PAGE_SIZE = 4096sys/dev/netif/wpi/if_wpireg.h:306βWPI_STAT_MAXLEN 20sys/config/X86_64_GENERIC:272βdevice wpi
Fix verification
not_testablecompile+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.
No comments yet.