ath_tdma_update passes the 0xff rateCodeToIndex sentinel into ath_hal_pkt_txtime β OOB read of rates->info[255] (64-entry table) and possible divide-by-zero panic from a received TDMA beacon
Summary
The rateCodeToIndex lookup returns 0xff for any hw rate code absent from the current table; rix is passed unvalidated to ath_hal_pkt_txtime which unconditionally reads rates->info[rateix] although info[] holds 64 entries - a ~2.3KB OOB read; garbage rateKbps in [1,124] with OFDM yields howmany(numBits,0) div-by-zero panic; garbage txtime propagates into nextslot u64 underflow and timer programming. Remote-adjacent (peer beacon in an admin-configured TDMA BSS; dynamic-turbo/mode-switch races acknowledged possible by the HAL's own comment). DF-1518-class. Validate-the-sentinel fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4722 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 741 B | β raw | |
| VERDICT.md | β | 4.2 KB | β raw | |
| fix.diff | β | 801 B | view raw | |
| verdict.json | β | 2.3 KB | view raw |
DF-4722 β PoC evidence pack
Finding: ath_tdma_update passes the 0xff sentinel from
rateCodeToIndex[] into ath_hal_pkt_txtime β OOB read rates->info[255]
(64-entry table), possible divide-by-zero panic.
Contents
VERDICT.mdβ full path:line mechanism (sentinel init, table size, HAL unguarded index, panic sub-path, trigger preconditions).verdict.jsonβ machine verdict (status: untested; hw-gated).fix.diffβ validate rix in the driver before the HAL call.
Reproduce / Expected
Needs an Atheros NIC in an admin-configured TDMA BSS receiving a beacon whose hw rate code is absent from the current rate table (dynamic-turbo / mode switch corners). Not reproducible on the QEMU guest (no radio).
DF-4722 β ath_tdma_update feeds the 0xff sentinel from rateCodeToIndex[] into HAL duration math β OOB read of rates->info[255] (table has 64 entries)
File: sys/dev/netif/ath/ath/if_ath_tdma.c Β· Severity: Low Β· Bucket: memcorrupt (OOB read) Β· Confidence: likely
Root cause (path:line)
ath_tdma_update() is the ic_tdma_update callback (if_ath.c:1030),
invoked by the net80211 layer while processing a received TDMA beacon
(wlan/ieee80211_tdma.c:506 β tdma_process_params β tdma_update β
ic_tdma_update, only in IEEE80211_S_RUN, i.e. attacker-adjacent remote
input once an admin has configured a TDMA BSS):
rs = sc->sc_lastrs; /* if_ath_tdma.c:397 */
...
rix = rt->rateCodeToIndex[rs->rs_rate]; /* if_ath_tdma.c:407 */
...
txtime = ath_hal_pkt_txtime(ah, rt, rs->rs_datalen,
rix, /* if_ath_tdma.c:432-436 */
!!(rs->rs_flags & HAL_RX_2040),
(rix & 0x80) ? (!(rs->rs_flags & HAL_RX_GI))
: rt->info[rix].shortPreamble);
rateCodeToIndex[256] is built by ath_hal_setupratetable()
(ath_hal/ah.c:568-584) with 0xff as the sentinel for every rate code
absent from the table. rs->rs_rate is the raw hardware rate code of the
received frame (ar5212_recv.c:276, ar5416_recv.c:200), which the HAL
comment itself says can be invalid "during dynamic Turbo transitions"
(ah.c:366-367 β same lookup family), and is not guaranteed to be present in
the current sc_currates table (mode/channel switch races, mixed-rate
frames from IBSS merges).
With rix == 0xff:
ath_hal_pkt_txtime()(ah.c:286-305) doesrc = rates->info[rateix].rateCodeunconditionally (ah.c:291) β OOB read atinfo[255]βHAL_RATE_TABLE.info[]has 64 entries (ah.h:681) β i.e. reads at byte offset 255sizeof(entry) (~3.0 KB) frominfo[0], ~2.3 KB beyond the end* of the array, pulling adjacent kernel data (static/heap rate tables or neighboring allocations).ath_hal_computetxtime()(ah.c:351-...) then readsinfo[0xff].rateCode/.rateKbps/.phy/.shortPreambleagain (4+ more OOB reads). If the garbagekbpsis in[1,124]and garbagephymatches an OFDM case,bitsPerSymbol == 0and β withHALASSERTcompiled out βnumSymbols = howmany(numBits, bitsPerSymbol)is a divide-by-zero panic; on INVARIANTS/debug builds theKASSERT(numStreams > 0 && <= 4)atah.c:301-303fires instead.- Otherwise the garbage
txtimepropagates intonextslot = (rstamp - txtime) + ...(if_ath_tdma.c:438) β withtxtime > rstampthe u64 underflows and the subsequentath_tdma_settimers()programs garbage beacon timers.
Note the driver's own ternary at :435-436 only accidentally avoids
rt->info[0xff] (0xff & 0x80 is true β GI branch); the HAL call is unguarded.
Impact
Remote-adjacent (peer in the admin-configured TDMA BSS) kernel OOB read
(~2.3 KB past a rate table) whose value drives timer arithmetic; possible
division-by-zero panic depending on adjacent-data layout; INVARIANTS builds
can KASSERT. No direct disclosure to userspace. Preconditions are narrow
(TDMA mode requires admin setup + HAL macversion > 0x78, if_ath.c:1028;
needs a rate-table/code mismatch such as dynamic turbo or mode-switch race β
hence Low/likely, consistent with DF-1518's treatment of the analogous
ar5416ChainTxDesc unvalidated-index OOB).
Verification
No Atheros NIC in the QEMU guest (and TDMA requires admin configuration on
real radio hardware) β static trace only. Mechanism fully pinned:
sentinel init ah.c:573-574, table size ah.h:681, unguarded index ah.c:291.
Recommended fix
Validate the lookup before use in if_ath_tdma.c (and ideally a HAL-side
guard in ath_hal_pkt_txtime): bail out of the timer update when
rix == 0xff. See fix.diff.
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 not kernel-validated: requires Atheros radio hardware in TDMA mode; not present in the QEMU guest.
fix.diff authored against read-only sys/.
Confirmed kernel references
- sys/dev/netif/ath/ath/if_ath_tdma.c:407
- sys/dev/netif/ath/ath/if_ath_tdma.c:432
- sys/dev/netif/ath/ath/if_ath_tdma.c:435
- sys/dev/netif/ath/ath_hal/ah.c:291
- sys/dev/netif/ath/ath_hal/ah.c:573
- sys/dev/netif/ath/ath_hal/ah.c:363
- sys/dev/netif/ath/ath_hal/ah.h:681
- sys/dev/netif/ath/ath_hal/ar5212/ar5212_recv.c:276
Detail
Evidence (decisive lines)
VERDICT.md (sentinel init, table size, unguarded HAL index, panic sub-path), fix.diff
PoC changes
n/a
Verified recommended fix
Bail out of ath_tdma_update when rateCodeToIndex returns the 0xff sentinel (and add a HAL-side bounds guard in ath_hal_pkt_txtime)
Verdict
ath_tdma_update (if_ath_tdma.c:355) looks up rix = rt->rateCodeToIndex[rs->rs_rate] at :407 for a received TDMA beacon and passes it unvalidated to ath_hal_pkt_txtime at :432; rateCodeToIndex is initialized with 0xff sentinels for absent codes (ah.c:573-574) while HAL_RATE_TABLE.info[] holds 64 entries (ah.h:681), so rix==0xff makes pkt_txtime read rates->info[255] (ah.c:291) ~2.3KB beyond the array, and computetxtime re-reads info[0xff] 4+ times with a divide-by-zero possible when the garbage rateKbps lands in [1,124] with an OFDM phy (HALASSERT compiled out). Remote-adjacent (peer beacons once an admin configured a TDMA BSS) but narrow preconditions (rate-table/code mismatch e.g. dynamic-turbo/mode-switch race, TDMA requires macversion>0x78 per if_ath.c:1028) -> Low/likely, consistent with DF-1518. No ath NIC in the QEMU guest -> static trace only.
No comments yet.