DWDS discovery frames staged on ic_stageq are leaked forever on age-out: ieee80211_node_timeout discards the mbuf list returned by ieee80211_ageq_age (remote kernel memory-exhaustion DoS)
Summary
ieee80211_dwds_discover stages every authorized station's 4-addr frame on ic->ic_stageq with a finite TTL. ieee80211_ageq_age() REMOVES expired frames and returns them as a list 'for the caller to reclaim', but ieee80211_node_timeout calls it as a bare statement - the returned list is dropped on the floor. Whenever discovery events are not serviced (no hostapd DWDS plumbing, daemon restart/race - wds_flush is the only consumer), every staged frame leaks mbuf + ~2KB cluster after its TTL. The aq_maxlen bound limits only backlog, not cumulative leak: an associated station (association is the only gate) sending a sustained 4-addr stream leaks ~rate x 2KB/s until kernel OOM. FreeBSD HEAD has the identical bare call - inherited defect. DF-0618 covered the KASSERT overflow in the same age expression; the leak at expiry was missed. Hardware-gated on the audit guest (no radio; evidence in pack) - path:line chain documented. Fix: consume the returned list with ieee80211_ageq_mfree.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3429 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.0 KB | β raw | |
| VERDICT.md | β | 2.8 KB | β raw | |
| trigger_stageq_leak.c | β | 2.4 KB | view raw | |
| run.sh | β | 139 B | view raw | |
| wlan_gate.txt | β | 137 B | view raw | |
| fix.diff | β | 1.0 KB | view raw | |
| verdict.json | β | 3.1 KB | view raw | |
| manifest.json | β | 939 B | view raw |
DF-3429 β DWDS discovery staged frames leak on age-out (remote DoS)
Build / Run
- No userland build: kernel-side defect.
./run.shβ documents the hardware gate on the audit guest (no radio attaches;ifconfig -Cshows no usable wlandev), so the path cannot be exercised in QEMU. All analysis is line-verified against both this tree and FreeBSD HEAD (identical defect inherited).
Mechanism (path:line)
- Attacker (authorized station of an AP vap) sends 4-address data frames.
hostap_inputβdir == DSTODS && ni->ni_wdsvap == NULL+ authorized βieee80211_dwds_discover(ni, m)(ieee80211_hostap.c:654-667).ieee80211_dwds_discover(ieee80211_wds.c:321-338) tags the mbuf with the MAC hash and appends it toic->ic_stageqwith a finite TTL.- If a WDS vap is plumbed,
wds_flush(ieee80211_wds.c:102-122) pulls the frames back out β no leak. - If NOT serviced,
ieee80211_node_timeoutfires everyIEEE80211_INACT_WAITand callsieee80211_ageq_age(&ic->ic_stageq, IEEE80211_INACT_WAIT)(ieee80211_node.c:2255) β return value (list of expired frames) ignored.ieee80211_ageq_age(ieee80211_ageq.c:162-185) removes the expired frames and returns them for the caller to free. They are dropped on the floor: permanent leak of mbuf + 2KB cluster per frame.
Impact
Remote, unauthenticated-after-association kernel memory exhaustion:
sustained 4-addr frames at rate R β R Γ 2KB/s leaked forever. Queue bound
(aq_maxlen = ic_max_keyix * 8) only bounds the backlog, not the leak.
Prerequisite: AP-mode vap with DWDS-capable setup and discovery events not
consumed (daemon absent/restarting) β a common field state.
Upstream
FreeBSD HEAD sys/net80211/ieee80211_node.c:2568 has the identical bare
ieee80211_ageq_age(&ic->ic_stageq, IEEE80211_INACT_WAIT); β inherited
defect; upstream disclosure recommended together with the DFly fix.
Verdict
inconclusive β mechanics certain from source; runtime verification
hardware-gated (QEMU guest has no 802.11 radio; wlan_gate.txt).
DF-3429 VERDICT β stageq expired-frame leak (DWDS discovery)
status: inconclusive | reproduced: 0 | impact: none demonstrated (hw-gated) | finding severity: Medium (remote memory-exhaustion DoS)
Reproduced?
No β and cannot be, on this guest: exercising the path requires an
802.11 radio (driver β ieee80211com β AP vap β associated station sending
4-addr frames). The QEMU guest has none: kldload if_iwm/if_run attach no
device, pciconf -l lists zero wireless controllers, ifconfig -C shows
the wlan cloner but no wlandev exists (wlan_gate.txt). The mechanics are
nevertheless certain from source, with an upstream cross-check:
ieee80211_ageq_age()(sys/netproto/802_11/wlan/ieee80211_ageq.c:162-185) removes expired frames and returns them as an m_nextpkt list ("The list of removed frames is returned to the caller" β its own comment,ageq.c:157-159).ieee80211_node_timeout()(ieee80211_node.c:2255) calls it as a bare statement β the returned list is unreachable β leaked (mbuf + cluster per frame; staged DWDS frames are raw RX frames, not M_ENCAP, so no node ref rides along β pure mbuf/cluster leak).- Producer:
ieee80211_dwds_discover(ieee80211_wds.c:333-337) appends every authorized station's 4-addr frame toic->ic_stageqwith a finite TTL ((ni_intval * ic_lintval << 2) / 1024s β the DF-0618 expression; normal values β tens of seconds). - Gate:
ieee80211_hostap.c:654-667β requiresieee80211_node_is_authorized(ni), i.e. attacker must complete association (+802.1X if enabled). Open/PSK networks: trivial. - FreeBSD HEAD comparison: identical bare call at
sys/net80211/ieee80211_node.c:2568(fetched 2026-09-12) β inherited.
Why "inconclusive" and not "not_reproduced"
The classification difference matters: nothing about the code failed to
behave β the environment cannot host the behavior at all. If a USB radio
were passed through, the trigger is: AP vap up, associate, send 4-addr
frames for ~TTL+20s with no WDS vap created, observe
netstat -m/mbstat cluster count monotonically increase with no mbuf
returned (or vmstat -m M_MBUFCL growth), repeatable across runs.
Fix
Consume the returned list in ieee80211_node_timeout:
struct mbuf *m; m = ieee80211_ageq_age(...); if (m != NULL) ieee80211_ageq_mfree(m);
(ieee80211_ageq_mfree applies M_ENCAP node-ref rules β ageq.c:87-98).
See fix.diff (against node.c). Fix-status not_testable (hw-gated).
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 ieee80211_node.c (consume the returned list); cannot be validated on the QEMU guest - no 802.11 hardware, so neither baseline nor patched behavior is exercisable.
fix.diff; wlan_gate.txt
Confirmed kernel references
Detail
Exploit chain
associate to AP vap (open/PSK) -> send 4-addr data frames (no WDS vap plumbed) -> frames stage on ic_stageq with ~40s TTL -> ageq_age expires them -> list leaked -> repeat at rate R leaks ~R*2KB/s until OOM/panic.
Evidence (decisive lines)
VERDICT.md (path:line chain + upstream comparison); trigger_stageq_leak.c (annotated); wlan_gate.txt (hardware gate on guest); fix.diff
PoC changes
No seed; authored annotated trigger. Guest attempt limited to proving the hardware gate (no radio => no wlan vaps).
Verified recommended fix
Capture and free the list returned by ieee80211_ageq_age() in ieee80211_node_timeout (ieee80211_ageq_mfree); see fix.diff.
Verdict
DWDS discovery frames staged on ic->ic_stageq by ieee80211_dwds_discover (ieee80211_wds.c:333-337, gated only by station authorization at ieee80211_hostap.c:654-667) are removed-and-returned by ieee80211_ageq_age() when their TTL expires, but ieee80211_node_timeout() calls it as a bare statement (ieee80211_node.c:2255) and drops the returned mbuf list on the floor: every unserviced discovery frame permanently leaks its mbuf + 2KB cluster. Sustained 4-addr traffic from an associated station = sustained kernel heap exhaustion (remote DoS). Runtime verification is hardware-gated: the QEMU audit guest has no 802.11 radio (kldload if_iwm/if_run attach nothing, zero wireless PCI devices - wlan_gate.txt), so no ieee80211com exists to exercise the path; mechanics are certain from source and the identical bare call exists in FreeBSD HEAD (ieee80211_node.c:2568), i.e. the defect is inherited from upstream.
No comments yet.