Remote heap overflow: unbounded SSID IE length copied into fixed 32-byte ni_essid
Summary
ieee80211_sta_join(:815-816) and ieee80211_init_neighbor(:1521-1522) copy sp->ssid[1] (attacker-controlled 0-255 from beacon/proberesp) into ni_essid[32] via memcpy. No check ssid[1]<=IEEE80211_NWID_LEN(32). Rate sets ARE guarded (scan_sta.c:285 KASSERT) but SSID has NO guard. Remote unauth: single crafted Beacon SSID len=192 -> 160 bytes overflow past ni_essid into ni_chan(ptr)/ni_rates/etc. Heap corruption -> panic or potential code execution. STA/IBSS/Mesh/TDMA all affected.
PoC verification
Evidence pack
findings/poc/DF-0326 ยท 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| node_overflow.c | trigger-source | userspace replica of struct ieee80211_node tail; runs exact :815-816/:1521-1522 memcpy, prints clobbered fields incl. ni_chan pointer | 9.5 KB | view raw |
| beacon_gen.c | exploit-chain | emits raw 802.11 Beacon with weaponized SSID IE (len=192); the radio-injection payload | 4.9 KB | view raw |
| beacon.bin | payload | crafted beacon (239 bytes) produced by beacon_gen 192; SSID IE len byte = 0xc0 | 239 B | view raw |
| build.sh | build-script | cc -O2 -Wall both programs | 350 B | view raw |
| run.sh | run-script | runs overflow proof + beacon gen + live-kernel reachability probe | 1.2 KB | view raw |
| build.log | build-log | full untrimmed final build output (BUILD_OK) | 227 B | view raw |
| run.log | run-log | full untrimmed final run output incl. offset table and clobber report | 6.8 KB | view raw |
| env.txt | environment | uname, cc, kldstat, ifconfig -l, nm symbol checks, host-side dead-flag proof | 2.8 KB | view raw |
| fix.diff | suggested-fix | git-apply-able diff: MIN(ssid[1], IEEE80211_NWID_LEN) at node.c:815,:1521 and scan_sta.c:284 | 1.6 KB | view raw |
| VERDICT.md | verdict | full narrative: line-by-line proof, dead-flag analysis, offset table, reachability, chain sketch | 8.9 KB | โ raw |
| README.md | readme | human-facing summary, build/run, expected output | 4.3 KB | โ raw |
DF-0326 โ ieee80211_node unbounded SSID heap overflow
Severity: High (CWE-787 Out-of-bounds Write)
Class: Remote, unauthenticated heap overflow via crafted 802.11 Beacon SSID IE.
Status: Real shipped bug in the live GENERIC kernel; unreachable on the KVM
audit guest (no 802.11 radio). See VERDICT.md for the full analysis.
The claim (DB-confirmed)
ieee80211_sta_join (sys/netproto/802_11/wlan/ieee80211_node.c:815-816) and
ieee80211_init_neighbor (:1521-1522) copy sp->ssid[1] (attacker-controlled
0โ255, from a Beacon/Probe-Response SSID IE) into ni_essid[32] via memcpy
with no check that ssid[1] <= IEEE80211_NWID_LEN (32). Rate sets ARE
guarded (scan_sta.c:285 KASSERT); the SSID is NOT. A single crafted Beacon
with ssid[1]=192 overflows 160 bytes past ni_essid into ni_rates and the
ni_chan kernel pointer. STA/IBSS/Mesh/TDMA all affected.
What's in this folder
| file | purpose |
|---|---|
VERDICT.md |
full narrative: line-by-line proof, dead-flag analysis, offset table |
node_overflow.c |
userspace replica of struct ieee80211_node tail; runs the exact memcpy and prints which fields (incl. ni_chan pointer) get clobbered for ssid_len โ |
beacon_gen.c |
emits a raw 802.11 Beacon frame with a weaponized SSID IE (default len=192) |
beacon.bin |
the crafted beacon produced by beacon_gen 192 (239 bytes) โ inject on real radio HW |
build.sh |
cc -O2 -Wall both programs |
run.sh |
runs the overflow proof, the beacon generator, and the live-kernel symbol/reachability probe |
build.log |
full untrimmed final build output |
run.log |
full untrimmed final run output |
env.txt |
guest uname, cc version, kldstat, ifconfig -l, nm symbol checks, and the host-side proof that BPARSE_SSID_INVALID is never consumed |
fix.diff |
standalone git apply-able diff clamping SSID len at all three sinks |
manifest.json |
machine-readable catalog |
Build & run (on the guest, no wifi required for the code-level proof)
ssh dfbsd-maxx # or copy the folder to any DragonFlyBSD host cd poc/DF-0326 ./build.sh && ./run.sh
Expected output (decisive lines)
From node_overflow:
ni_essid[32] off=29 size=32 [SINK] ni_rates off=61 size=16 ni_chan (POINTER) off=80 size=8 (kernel POINTER) ni_essid -> ni_chan distance = 51 bytes => an SSID of length >= 59 fully overwrites ni_chan. === overflow with ssid_len = 192 (ni_esslen now 192) === ni_essid[32] ... [SINK] overflows past end ni_rates ... CLOBBERED ni_chan (POINTER) ... CLOBBERED ptr=0xcccccccccccccccc <-- ATTACKER-CONTROLLED KERNEL POINTER ... 160 byte OVERFLOW; 8/8 bytes of the channel pointer are attacker-controlled
From run.sh reachability probe:
ffffffff8078c4c0 T ieee80211_add_scan ffffffff8077dc50 T ieee80211_init_neighbor ffffffff8077d1f0 T ieee80211_sta_join --- 802.11 interfaces present (radio check) --- vtnet0 lo0
i.e. the vulnerable code IS compiled into the live kernel, but there is no wlan
interface to drive it โ hence inconclusive (real bug, no radio).
Live reproduction (requires real 802.11 hardware)
./beacon_gen 192 > beacon.bin # inject beacon.bin from a monitor+injection-capable wlan vap (e.g. scapy, # lorcon, or a custom raw socket) while the victim STA/IBSS/Mesh is scanning. # expect: kernel panic from corrupted ni_chan deref, or silent heap corruption.
Notes
- The
IEEE80211_VERIFY_ELEMENT(...,SSID_INVALID)atieee80211_input.c:680looks like a guard but is not: theIEEE80211_BPARSE_SSID_INVALIDbit it sets is never read by any consumer (grep confirms only set + define; seeenv.txt). The rate flag and off-channel flag ARE consumed; the SSID flag is dead. That is the root-cause asymmetry. node_overflow.cis line-faithful toieee80211_node.h:176-196; offsets are the real LP64 layout (pointer = 8 bytes,x86_64confirmed byuname).
DF-0326 โ ieee80211_node SSID heap overflow (code-level verification)
Verdict: INCONCLUSIVE (real shipped bug, unreachable without 802.11 radio)
The vulnerability is real and present in the live GENERIC kernel, confirmed
by line-by-line source tracing and a faithful userspace replica of the
struct ieee80211_node overflow. It is not live-reproducible on this KVM
guest only because the guest has no 802.11 interface (ifconfig -l โ
vtnet0 lo0); the vulnerable code is 802.11 frame-receive-path only and
cannot be driven from vtnet0/lo0 or via netgraph/hostap/loopback. On any
DragonFlyBSD host with a wlan device (STA/IBSS/Mesh/TDMA), a single crafted
Beacon triggers it remotely, unauthenticated.
The bug โ confirmed line by line
Two sinks copy the attacker-controlled SSID length byte ssid[1] (range 0โ255,
taken verbatim from a Beacon/Probe-Response Information Element) into the
fixed 32-byte ni_essid[] via memcpy with no bounds check:
sys/netproto/802_11/wlan/ieee80211_node.c:815-816(ieee80211_sta_join, STA join):c ni->ni_esslen = se->se_ssid[1]; memcpy(ni->ni_essid, se->se_ssid+2, ni->ni_esslen); /* ni_esslen unbounded */sys/netproto/802_11/wlan/ieee80211_node.c:1521-1522(ieee80211_init_neighbor, IBSS/Mesh/TDMA):c ni->ni_esslen = sp->ssid[1]; memcpy(ni->ni_essid, sp->ssid + 2, sp->ssid[1]); /* sp->ssid[1] unbounded */
The sink buffer is declared at ieee80211_node.h:188 as
uint8_t ni_essid[IEEE80211_NWID_LEN]; where IEEE80211_NWID_LEN == 32
(ieee80211.h:199). ssid[1] may legitimately be 0โ255, so any value > 32
overflows the buffer.
A third identical-class sink exists upstream and feeds sta_join:
- sys/netproto/802_11/wlan/ieee80211_scan_sta.c:282-284 (ieee80211_add_scan):
c
if (sp->ssid[1] != 0 && (ISPROBE(subtype) || ise->se_ssid[1] == 0))
memcpy(ise->se_ssid, sp->ssid, 2+sp->ssid[1]); /* se_ssid[34] overflow */
(se_ssid is uint8_t[2+IEEE80211_NWID_LEN] = 34 bytes, ieee80211_scan.h:264.)
The unbounded se_ssid[1] then flows into sta_join (se->se_ssid[1] at :815).
Why the "length check" does not help โ the dead flag
ieee80211_input.c:680-681 appears to validate the SSID:
IEEE80211_VERIFY_ELEMENT(scan->ssid, IEEE80211_NWID_LEN,
scan->status |= IEEE80211_BPARSE_SSID_INVALID);
But IEEE80211_VERIFY_ELEMENT (ieee80211_input.h:31-43) only logs a discard
message, increments a stat counter, and sets the IEEE80211_BPARSE_SSID_INVALID
status bit. It does not clamp ssid[1], null the pointer, or return. The
oversized SSID therefore survives into the scan table and into both node sinks.
Decisive proof โ a grep of the entire sys/ tree shows the flag is a dead bit:
sys/netproto/802_11/wlan/ieee80211_input.c:680 IEEE80211_VERIFY_ELEMENT(...SSID_INVALID) <- SET here sys/netproto/802_11/ieee80211_scan.h:209 IEEE80211_BPARSE_SSID_INVALID = 0x08 <- DEFINED here
It is never read by any consumer. By contrast, the sibling flags
IEEE80211_BPARSE_OFFCHAN and IEEE80211_BPARSE_RATES_INVALID are gated on
(scan_sta.c:296,319; hostap.c:1707,1768). The rate set is additionally
enforced by a hard KASSERT(sp->rates[1] <= IEEE80211_RATE_MAXSIZE, ...)
(scan_sta.c:285); the SSID has no equivalent KASSERT and no live
consumer of its invalid flag. The asymmetry is the root cause.
What gets clobbered โ ni_chan pointer CONFIRMED
node_overflow.c replicates the tail of struct ieee80211_node verbatim
(ieee80211_node.h:176-196) and runs the exact memcpy from :1521-1522 /
:815-816 on the live LP64 ABI. Measured offsets (from the start of the replica):
| field | offset | size | notes |
|---|---|---|---|
ni_esslen |
28 | 1 | written first (== ssid[1]) |
ni_essid[32] |
29 | 32 | SINK |
ni_rates |
61 | 16 | clobbered (offset 32 from essid) |
ni_chan (POINTER) |
80 | 8 | fully attacker-controlled |
ni_fhdwell |
88 | 2 | clobbered |
ni_fhindex |
90 | 1 | clobbered |
ni_erp |
92 | 2 | clobbered |
ni_timoff |
94 | 2 | clobbered |
ni_dtim_period |
96 | 1 | clobbered |
ni_dtim_count |
97 | 1 | clobbered |
ni_meshidlen |
98 | 1 | clobbered |
ni_meshid[32] |
99 | 32 | clobbered |
ssid_len = 48โ 16-byte overflow, clobbersni_ratesonly.ssid_len = 56โ 24-byte overflow, clobbers 5/8 bytes ofni_chan.ssid_len = 59is the minimum to fully overwriteni_chan(offset 51 fromni_essid, +8).ssid_len = 192(the finding's cited payload) โ 160-byte overflow, fully clobberingni_rates, the entireni_chankernel pointer (8/8 bytes), and 8 further fields throughni_meshid[32].ssid_len = 255(max) โ 223-byte overflow.
The finding's ni_chan-pointer and ni_rates overflow claims are CONFIRMED.
ni_chan is a struct ieee80211_channel * (ieee80211_node.h:190), dereferenced
throughout the wlan stack (e.g. ni_chan->ic_freq, ic_ieee, rate/channel
lookups). A crafted Beacon that writes a controlled 8-byte value into ni_chan
yields an arbitrary kernel-pointer dereference on the next channel/rate query for
that node โ a clean memory-corruption primitive.
Reachability on the live kernel
All three functions are compiled into the base kernel (device wlan is in
sys/config/X86_64_GENERIC:258), confirmed by nm /boot/kernel/kernel:
ffffffff8078c4c0 T ieee80211_add_scan ffffffff8077dc50 T ieee80211_init_neighbor ffffffff8077d1f0 T ieee80211_sta_join ffffffff8076ce50 T ieee80211_parse_beacon
The code is live. The only thing preventing exploitation on this guest is the
absence of an 802.11 radio: ifconfig -l shows vtnet0 lo0 only. The vulnerable
helpers are wlan receive-path only (ieee80211_parse_beacon โ ieee80211_add_scan
โ ieee80211_sta_join; and ieee80211_recv_mgmt โ ieee80211_init_neighbor via
ieee80211_adhoc.c:741). There is no netgraph/hostap/loopback path that reaches
them without a wlan vap attached to a real driver. Reproduction requires either a
wlan NIC passed through to the guest, or an injection harness on real radio
hardware emitting beacon.bin (this folder) in monitor mode while the victim
STA/IBSS is scanning.
Exploitability (chain sketch, not developed live)
Primitive class: remote, unauthenticated, attacker-controlled heap overflow
into a kernel object (struct ieee80211_node is kmalloc-allocated via
ieee80211_alloc_node). Overwrite of ni_chan (channel pointer) is the high-
value target:
- Beacon with
ssid_lenโฅ 59 writes a fully-controlled 8-byte kernel pointer intoni_chan. - On the next operation dereferencing
ni_chan(channel/frequency lookup, rate-control setupieee80211_node_setuptxparmsโni_chan->ic_*, orieee80211_ratectl_node_init), the attacker controls the pointer base, yielding an arbitrary kernel read, or โ if the forged "channel" object is itself groomed โ a type-confused dereference toward code execution. - Larger overflows (len 192/255) additionally clobber rate state, mesh ID, and DTIM fields, broadening corruption.
ni_chan is the strongest lever; a full RCE chain would require heap-grooming a
fake struct ieee80211_channel and steering a function-pointer-bearing field
into the dereference path (not pursued here โ no radio in the guest). Even
without a chain, the corruption reliably yields a panic (null/bogus pointer
deref) โ i.e. unauthenticated remote DoS at minimum.
Fix
fix.diff clamps the SSID length to IEEE80211_NWID_LEN at all three sinks
using MIN() (from <sys/param.h>, already included):
ieee80211_node.c:815โni->ni_esslen = MIN(se->se_ssid[1], IEEE80211_NWID_LEN);ieee80211_node.c:1521โni->ni_esslen = MIN(sp->ssid[1], IEEE80211_NWID_LEN);(+ useni->ni_esslenin the memcpy)ieee80211_scan_sta.c:284โ2 + MIN(sp->ssid[1], IEEE80211_NWID_LEN)(defense-in-depth on the upstream sink)
Verified git apply --check and patch --dry-run -p1 both succeed; sys/ is
untouched. A defense-in-depth follow-up would be to make
IEEE80211_VERIFY_ELEMENT for the SSID actually reject the frame (e.g. null
scan->ssid when ssid[1] > IEEE80211_NWID_LEN), but the clamp at the three
memcpy sinks is the minimal, root-cause fix.
Reproduce
cd findings/poc/DF-0326 ./build.sh && ./run.sh # code-level proof + crafted beacon + live-kernel probe
On real 802.11 hardware: ./beacon_gen 192 > beacon.bin, then inject beacon.bin
via a monitor+injection-capable wlan vap while the victim is scanning/peering.
Confirmed kernel references
- sys/netproto/802_11/wlan/ieee80211_node.c:815
- sys/netproto/802_11/wlan/ieee80211_node.c:816
- sys/netproto/802_11/wlan/ieee80211_node.c:1521
- sys/netproto/802_11/wlan/ieee80211_node.c:1522
- sys/netproto/802_11/wlan/ieee80211_scan_sta.c:284
- sys/netproto/802_11/wlan/ieee80211_scan_sta.c:285
- sys/netproto/802_11/wlan/ieee80211_input.c:680
- sys/netproto/802_11/ieee80211_input.h:31
- sys/netproto/802_11/ieee80211_node.h:188
- sys/netproto/802_11/ieee80211_node.h:190
- sys/netproto/802_11/ieee80211.h:199
- sys/netproto/802_11/ieee80211_scan.h:209
- sys/config/X86_64_GENERIC:258
Detail
Exploit chain
Primitive: remote unauthenticated attacker-controlled heap overflow into a kmalloc'd struct ieee80211_node. ni_chan (struct ieee80211_channel ) is the high-value target -- a Beacon with ssid_len>=59 writes a fully-controlled 8-byte kernel pointer there, dereferenced on the next channel/rate query (ni_chan->ic_, ieee80211_node_setuptxparms, ieee80211_ratectl_node_init), yielding arbitrary kernel read or type-confused deref toward RCE; at minimum a reliable panic (remote DoS). Not developed live (no radio); chain sketch and the weaponized beacon.bin payload are in the folder.
Evidence (decisive lines)
run.log shows node_overflow printing 'ni_chan (POINTER) ... CLOBBERED ptr=0xcccccccccccccccc <- ATTACKER-CONTROLLED KERNEL POINTER' and '160 byte OVERFLOW; 8/8 bytes of the channel pointer are attacker-controlled' at ssid_len=192. env.txt shows nm /boot/kernel/kernel listing ieee80211_sta_join/init_neighbor/add_scan as T symbols AND ifconfig -l=vtnet0 lo0 (no radio). env.txt host-side section proves BPARSE_SSID_INVALID is only SET (input.c:680) and DEFINED (scan.h:209), never consumed. fix.diff clamps all three sinks and passes git apply --check. beacon.bin (239 bytes) carries SSID IE len byte 0xc0.
PoC changes
Authored the entire evidence pack from scratch (no prior folder existed). node_overflow.c: userspace replica of struct ieee80211_node tail (ieee80211_node.h:176-196) that runs the exact :815-816/:1521-1522 memcpy and prints clobbered fields/offsets for ssid_len in {32,48,56,192,255}. beacon_gen.c: emits a raw 802.11 Beacon with weaponized SSID IE (len byte 0xc0) -> beacon.bin for radio injection. build.sh/run.sh, VERDICT.md, README.md, manifest.json, env.txt, fix.diff (MIN(ssid[1],IEEE80211_NWID_LEN) at node.c:815,:1521 and scan_sta.c:284).
Verified recommended fix
fix.diff clamps the SSID length at all three memcpy sinks using MIN(ssid[1], IEEE80211_NWID_LEN) (MIN from
Verdict
Real shipped bug, confirmed line-by-line, but not live-triggerable on this guest (no 802.11 radio). ieee80211_sta_join (:815-816) and ieee80211_init_neighbor (:1521-1522) memcpy attacker-controlled ssid[1] (0-255) into ni_essid[32] with no bound; the IEEE80211_VERIFY_ELEMENT check at input.c:680 only sets the IEEE80211_BPARSE_SSID_INVALID flag, which grep proves is NEVER read by any consumer (a dead bit), unlike the KASSERT-guarded rates (:285) and the consumed OFFCHAN flag. A userspace replica of struct ieee80211_node (node_overflow.c) confirms ssid_len=192 -> 160-byte overflow that fully clobbers ni_rates (off 61) and the ni_chan POINTER (off 80, 8/8 attacker-controlled bytes) plus 8 further fields. CONFIRMED: the ni_chan-pointer and ni_rates overflow claims hold (ni_chan is 51 bytes past ni_essid, fully overwritten at ssid_len>=59). All three functions are T symbols in the live GENERIC kernel (device wlan, X86_64_GENERIC:258), but ifconfig -l shows only vtnet0 lo0 so the receive path cannot be driven here.