β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4722

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4722 Β· 4 files
FileTypeDescriptionSize
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).

VERDICT.md
↓ download raw

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:

  1. ath_hal_pkt_txtime() (ah.c:286-305) does rc = rates->info[rateix].rateCode unconditionally (ah.c:291) β†’ OOB read at info[255] β€” HAL_RATE_TABLE.info[] has 64 entries (ah.h:681) β€” i.e. reads at byte offset 255sizeof(entry) (~3.0 KB) from info[0], ~2.3 KB beyond the end* of the array, pulling adjacent kernel data (static/heap rate tables or neighboring allocations).
  2. ath_hal_computetxtime() (ah.c:351-...) then reads info[0xff].rateCode/.rateKbps/.phy/.shortPreamble again (4+ more OOB reads). If the garbage kbps is in [1,124] and garbage phy matches an OFDM case, bitsPerSymbol == 0 and β€” with HALASSERT compiled out β€” numSymbols = howmany(numBits, bitsPerSymbol) is a divide-by-zero panic; on INVARIANTS/debug builds the KASSERT(numStreams > 0 && <= 4) at ah.c:301-303 fires instead.
  3. Otherwise the garbage txtime propagates into nextslot = (rstamp - txtime) + ... (if_ath_tdma.c:438) β€” with txtime > rstamp the u64 underflows and the subsequent ath_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.

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_testable
baseline no→ patch + rebuild →patched clean

Fix not kernel-validated: requires Atheros radio hardware in TDMA mode; not present in the QEMU guest.

fix.diff authored against read-only sys/.
↓ fix.diffper-fix-DF-4722

Confirmed kernel references

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.