โฌข DragonFlyBSD Kernel Audit
โ† dashboard
DF-0326

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
FileTypeDescriptionSize
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
README.md readme human-facing summary, build/run, expected output
โ†“ download 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) at ieee80211_input.c:680 looks like a guard but is not: the IEEE80211_BPARSE_SSID_INVALID bit it sets is never read by any consumer (grep confirms only set + define; see env.txt). The rate flag and off-channel flag ARE consumed; the SSID flag is dead. That is the root-cause asymmetry.
  • node_overflow.c is line-faithful to ieee80211_node.h:176-196; offsets are the real LP64 layout (pointer = 8 bytes, x86_64 confirmed by uname).
VERDICT.md verdict full narrative: line-by-line proof, dead-flag analysis, offset table, reachability, chain sketch
โ†“ download raw

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:

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, clobbers ni_rates only.
  • ssid_len = 56 โ†’ 24-byte overflow, clobbers 5/8 bytes of ni_chan.
  • ssid_len = 59 is the minimum to fully overwrite ni_chan (offset 51 from ni_essid, +8).
  • ssid_len = 192 (the finding's cited payload) โ†’ 160-byte overflow, fully clobbering ni_rates, the entire ni_chan kernel pointer (8/8 bytes), and 8 further fields through ni_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:

  1. Beacon with ssid_len โ‰ฅ 59 writes a fully-controlled 8-byte kernel pointer into ni_chan.
  2. On the next operation dereferencing ni_chan (channel/frequency lookup, rate-control setup ieee80211_node_setuptxparms โ†’ ni_chan->ic_*, or ieee80211_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.
  3. 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); (+ use ni->ni_esslen in 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

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 ): ieee80211_node.c:815 (sta_join), ieee80211_node.c:1521 (init_neighbor), and ieee80211_scan_sta.c:284 (add_scan, defense-in-depth on the upstream sink). Verified git apply --check + patch --dry-run pass; sys/ untouched. Supersedes any finding proposal by also fixing the upstream scan_sta.c:284 sink and the init_neighbor memcpy-arg. Defense-in-depth follow-up: make IEEE80211_VERIFY_ELEMENT actually reject (null scan->ssid) when ssid[1]>IEEE80211_NWID_LEN instead of setting the dead BPARSE_SSID_INVALID flag.

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.