tdma_isfull()/match_bss() heap out-of-bounds read via unvalidated tdma_ie slot count (up to ~30 bytes past the blob allocation)
Summary
ies_expand exposes se_ies.tdma_ie for any TDMA-OUI vendor IE with ie[1] >= 4 only. match_bss()'s AHDEMO+TDMA branch reads tdma_version (offset 7), tdma_slot (8), tdma_slotcnt (9) unvalidated, and tdma_isfull() scans isclr(tdma->tdma_inuse, slot) for slot down from the attacker-controlled slotcnt-1 although tdma_inuse[] is 1 byte - slotcnt=255 reads tdma_inuse[31] = IE byte 44, up to 30 bytes past the blob allocation when the IE is last. match_bss also dereferences vap->iv_tdma with no NULL check. Config-dependent reachability (AHDEMO+TDMA vap, root-created; TDMA compiled into X86_64_GENERIC). PROVEN with kernel-internal probes on the real code: identical beacons, only the heap bytes past the allocation differ (0x00 vs 0xff) -> candidate ACCEPTED (clear at slot 254 read from byte +44) vs REJECTED (FULL); GCC -Warray-bounds independently rejects tdma_inuse[31] as above the declared array bounds. No uid0 route (read-only oracle). Fix: require a complete parameters IE + clamp the scan to 8*nitems(tdma_inuse) + NULL-check iv_tdma.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3364 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fwscan.c | β | 29.5 KB | view raw | |
| Makefile | β | 263 B | β download | |
| build.sh | β | 87 B | view raw | |
| build.log | β | 5.6 KB | view raw | |
| run.log | β | 2.5 KB | view raw | |
| README.md | β | 2.4 KB | β raw | |
| fix.diff | β | 2.0 KB | view raw | |
| verdict.json | β | 3.8 KB | view raw | |
| manifest.json | β | 684 B | view raw |
DF-3364 β tdma_isfull()/match_bss() heap OOB read via unvalidated tdma_ie slot count
File: sys/netproto/802_11/wlan/ieee80211_scan_sta.c (pass 2)
What
ieee80211_ies_expand() exposes se_ies.tdma_ie for any vendor IE
carrying the TDMA OUI β istdmaoui() only requires ie[1] > 3
(ieee80211_input.h:118-121). match_bss()'s AHDEMO+TDMA branch
(ieee80211_scan_sta.c:1016-1028) then reads tdma_version (IE offset
7), tdma_slot (8), tdma_slotcnt (9) and calls tdma_isfull()
(:959-969), which scans isclr(tdma->tdma_inuse, slot) for slot down
from the attacker-controlled slotcnt-1 although tdma_inuse[]
is 1 byte (8 slots) and the whole IE may be as short as 6 bytes.
With slotcnt = 255 the reads reach tdma_inuse[31] = IE byte 44 =
up to 30 bytes past the end of the exactly-sized blob allocation
when the TDMA IE is the blob's last IE. tdma->tdma_version (offset
7) is already past a minimal OUI-only IE.
Reproduced (instrumented kernel #1 β stock logic + kprintf probes only)
AHDEMO+TDMA vap (ifconfig wlan create wlandev fws0 wlanmode tdma up;
TDMA + MESH are compiled into X86_64_GENERIC, sys/config/X86_64_GENERIC:257).
Beacon C: TDMA master (slot 0, version 2 == vap's), inuse[0]=0xff
(all 8 legal slots busy), slotcnt=255, TDMA IE last in a 64-byte blob:
- groom 0x00 past the allocation: kernel
tdma_isfullprintedinuse[31]=00βclear at slot 254 (byte +44)βfail=0x0β C ACCEPTED β the acceptance came from an out-of-bounds byte. - groom 0xff:
inuse[31]=ffβFULLβfail=0x1000(MATCH_TDMA_NOSLOT) β C REJECTED. - control slotcnt=8: decision from in-bounds bytes only.
GCC itself corroborates the class: instrumenting the read as
tdma_inuse[31] trips -Werror=array-bounds ("array subscript 31 is
above array bounds of const u_int8_t[1]").
Threat
Requires an AHDEMO+TDMA vap (root-created, TDMA-capable driver) β config-dependent reachability, but unauthenticated beacon input. Impact: heap OOB read β€ ~30 bytes whose value flips candidate acceptance (same oracle grade as DF-3363).
Build / run
Same harness as DF-3363 (fwscan.ko): sysctl kern.fwscan_test=11|12|14|17
with the tdma vap. Fix validated together with DF-3363 (see
../DF-3363/fix_validation.log): post-fix all short TDMA IEs are
rejected (NOIE), a full-length valid TDMA IE (test 17) is accepted.
Fix verification
fixedwith fix.diff, the 12-byte TDMA IE variants (11/12/14) are all rejected (MATCH_TDMA_NOIE) regardless of heap groom; the full-length valid IE (17) is accepted. No OOB-dependent behavior remains.
findings/poc/DF-3363/fix_validation.log
Confirmed kernel references
Detail
Exploit chain
unauthenticated beacon to an AHDEMO+TDMA vap (root-created, driver with C_TDMA) -> short TDMA-OUI vendor IE saved verbatim in the blob -> ies_expand exposes tdma_ie (len>=4 only) -> match_bss reads tdma_version/slot past minimal IEs -> tdma_isfull scans 248 attacker-directed bitmap bytes beyond the allocation -> OOB heap bytes decide candidate acceptance. Oracle-grade; no write primitive.
Evidence (decisive lines)
["run.log: 'DBG isfull: ... slotcnt=255 inuse[0]=ff inuse[31]=00 / clear at slot 254 (byte +44)' -> ACCEPTED vs 'inuse[31]=ff / FULL / fail=0x1000' -> REJECTED", '../DF-3363/fix_validation.log: post-fix TDMA matrix (17 accepted, 11/12/14 rejected as malformed)', 'build.log excerpt in VERDICT: gcc -Werror=array-bounds rejects tdma_inuse[31] as above array bounds']
PoC changes
same harness as DF-3363 plus: TDMA vap must be created via 'wlanmode tdma' (ifconfig clone flag IEEE80211_CLONE_TDMA); C's rates must include 11 Mbps (the TDMA vap pins ucastrate=2*11 via TDMA_TXRATE_11B_DEFAULT); IEEE80211_SCAN_NOJOIN used so the selection verdict is observable without driving the TDMA join state machine (which hangs on the fake radio); the flaky early runs were background-scan se_notseen bumps, fixed by re-injecting C right before scan_end.
Verified recommended fix
require tdma_len >= sizeof(struct ieee80211_tdma_param)-2 before reading fields, clamp slotcnt to 8*nitems(tdma_inuse); see fix.diff.
Verdict
tdma_isfull() (ieee80211_scan_sta.c:965) indexes tdma_inuse[slot>>3] for slot up to the attacker-controlled slotcnt-1 (255) although the array is one byte inside an IE that can be as short as 6 bytes; with the TDMA IE last in a 64-byte blob the kernel read tdma_inuse[31] = IE byte 44 = 30 bytes past the heap allocation, and that byte's content flipped the candidate decision: 0x00 groom -> 'clear at slot 254' -> fail=0x0 -> C ACCEPTED; 0xff groom -> FULL -> fail=0x1000 -> C REJECTED. Proven with kprintf instrumentation inside the real tdma_isfull/match_bss on an otherwise-stock kernel (probe-only patch); gcc -Warray-bounds independently flags the same index as out-of-bounds. Post-fix (fix.diff kernel #2) all short TDMA IEs are rejected as malformed and the full-length valid IE (test 17) is accepted.
No comments yet.