Unvalidated hardware-fed ts_antenna indexes ast_ant_tx[8]/sc_ant_tx[8] in ath_tx_update_stats β latent OOB write with function-pointer-table and past-allocation reach (RX twin clamps after field crashes)
Summary
ath_tx_update_stats() increments sc->sc_stats.ast_ant_tx[ts->ts_antenna] and sc->sc_ant_tx[ts->ts_antenna] using the raw hardware-supplied uint8_t from the TX-status descriptor with no clamp. Both arrays are 8 entries inside the 13392-byte ath_softc. The RX-side twin of this exact pattern clamps rs_antenna>7 because AR9285 NICs shipped 'bogusly large' values and crashed users; the TX side has no such defense. Latent today (every in-tree HAL emits a bounded value) but the guard exists on RX precisely because HAL/hardware bugs of this class reached users. MEASURED on the guest via a KLD built in the kernel environment: ast_ant_tx@7820 so indices 183-246 land exactly on the sc_rx/sc_tx function-pointer method tables - wild indirect call; sc_ant_tx@12832 so index 8 hits the sc_cabq pointer and indices >140 write past the softc heap allocation (max reach 13852 > sizeof 13392). A malicious/compromised NIC paces this remotely via TX traffic. No runnable PoC without Atheros hardware (in-tree HALs emit antenna<=2 - documented as hard-blocked).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3615 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.3 KB | β raw | |
| VERDICT.md | β | 3.0 KB | β raw | |
| offmod.c | β | 1.8 KB | view raw | |
| Makefile | β | 120 B | β download | |
| build.sh | β | 121 B | view raw | |
| build.log | β | 1.2 KB | view raw | |
| run.log | β | 1.8 KB | view raw | |
| offsets_dmesg.txt | β | 929 B | view raw | |
| env.txt | β | 388 B | view raw | |
| fix.diff | β | 873 B | view raw | |
| manifest.json | β | 1.6 KB | view raw | |
| verdict.json | β | 3.1 KB | view raw |
DF-3615 β unvalidated ts_antenna indexes ast_ant_tx[8]/sc_ant_tx[8] (latent OOB write)
What the finding is
ath_tx_update_stats() (sys/dev/netif/ath/ath/if_ath.c:4196-4199) does:
if (ts->ts_status == 0) {
u_int8_t txant = ts->ts_antenna;
sc->sc_stats.ast_ant_tx[txant]++; /* ast_ant_tx[8] */
sc->sc_ant_tx[txant]++; /* sc_ant_tx[8] */
ts->ts_antenna is a raw uint8_t from the HAL TX-status descriptor
(sys/dev/netif/ath/ath_hal/ah_desc.h:56, comment: "may not be valid for all
h/w"). No clamp. The RX twin of this exact code CLAMPS the same-class field
(sys/dev/netif/ath/ath/if_ath_rx.c:828-835) because "Some users w/ AR9285
NICs have reported crashes here because rs_antenna field is bogusly large"
(upstream field-reported crashes, fixed by clamping at the driver).
Current triggerability (why this is latent, not live)
Every HAL in this tree writes a bounded value into ts_antenna: - ar5210_xmit.c:614 -> 0 ("NB: don't know") - ar5211_xmit.c:645 -> 0 - ar5212_xmit.c:943 -> 1 or 2 - ar5416_xmit.c:814 -> 0 ("don't switch antennas on Owl") - contrib ar9300_xmit_ds.c:354 -> 0 So no in-tree configuration can emit antenna>7 today; the bug fires the day a HAL (or a HAL bug) forwards a raw descriptor byte β exactly the AR9285 RX-side failure mode this driver family already shipped to users.
Measured corruption geometry (guest evidence)
See offsets_dmesg.txt (produced by offmod.c / build.sh / run.log on the audit guest, DragonFly 6.5-DEVELOPMENT X86_64_GENERIC):
sizeof(ath_softc) = 13392 sc_stats.ast_ant_tx = 7820 (array [8] ends 7852) sc_rx methods (fn ptrs)= 8552 -> index (8552-7820)/4 = 183 sc_tx methods (fn ptrs)= 8736 -> index (8736-7820)/4 = 229 sc_txedma = 8784 sc_ant_tx = 12832 (array [8] ends 12864) sc_cabq (pointer) = 12864 -> index 8 EXACTLY (first OOB slot) sc_bslot[] (vap ptrs) = 13040 -> indices 52..59 sc_cal_ch (callout) = 13080 -> index 62 sc_eepromdata (ptr) = 13168 end of object = 13392 -> indices >140 run PAST the softc heap alloc
Consequences if a value >7 ever flows in:
- ast_ant_tx[txant]++ with 183<=txant<=246 increments the sc_rx/sc_tx
method-table function pointers (wild indirect call on next dispatch).
- sc_ant_tx[txant]++ with txant==8 increments the sc_cabq pointer itself
(dereferenced as sc->sc_cabq->axq_qnum in ath_tx_proc_q0/q0123).
- txant>140 writes past the end of the 13392-byte softc allocation into
adjacent kernel heap (increment primitive, +1 per TX completion β
high-frequency, remotely-driven cadence).
Build / run (offset measurement only β no ath hardware needed)
./build.sh # builds off.ko against the X86_64_GENERIC obj tree kldload ./off.ko ; dmesg | grep OFF ; kldunload off
Expected output
The OFF lines in offsets_dmesg.txt / run.log.
Why the corruption itself is not reproduced here
The audit guest has no Atheros NIC (see env.txt: no ath PCI device, no dev.ath sysctl, if_ath.ko present in /boot/kernel but unloaded). The corrupting write sits on the TX-completion interrupt path, which cannot run without the hardware; and with the in-tree HALs ts_antenna never exceeds 2. Honest verdict: inconclusive β defect is source-certain, execution is not possible on this guest.
DF-3615 β VERDICT
Status: inconclusive (defect source-certain; not executable on this guest)
Reproduced?
The corrupting write was NOT reproduced β it cannot be: the guest has no
Atheros hardware (env.txt: pciconf -l has no ath device, dev.ath sysctl
count 0), and the write sits on the hardware TX-completion path
(ath_intr -> sc_txtask -> ath_tx_processq -> ath_tx_process_buf_completion
-> ath_tx_update_stats, sys/dev/netif/ath/ath/if_ath.c:4188-4199).
Additionally, with the HALs currently in-tree the index never exceeds 2
(ar5210/ar5211/ar5416/ar9300 write constant 0; ar5212 writes 1..2), so even
with a NIC the OOB does not fire today β it is a latent driver-side
hardening gap against the exact failure mode that already crashed users on
the RX side (if_ath_rx.c:809-835 comment: AR9285 "rs_antenna field is
bogusly large" -> crashes; upstream added the RX clamp).
What WAS verified on the guest
The corruption geometry was measured, not guessed: a KLD (offmod.c) built inside the guest's kernel build environment (make -DNO_WERROR against /usr/obj/usr/src/sys/X86_64_GENERIC, logs in build.log/run.log) printed the real ath_softc layout β offsets_dmesg.txt:
- sizeof(ath_softc)=13392; sc_stats.ast_ant_tx=7820; sc_ant_tx=12832
- index 183..246 from ast_ant_tx hits the sc_rx/sc_tx function-pointer method tables (8552/8736)
- index 8 from sc_ant_tx hits the sc_cabq pointer exactly (12864)
- index >140 from sc_ant_tx passes the end of the softc heap allocation (13852 > 13392) β adjacent-heap increment primitive
Module presence was also verified: if_ath.ko / ath_hal.ko / ath_rate.ko / ath_dfs.ko ship in /boot/kernel (compiled for the stock kernel) β the code is live attack surface on any machine with an Atheros NIC, not dead code.
Exploit chain (if ever triggerable)
hardware/HAL emits antenna=N>7 -> sc->sc_stats.ast_ant_tx[N]++ /
sc->sc_ant_tx[N]++ on every successful TX completion -> repeated +1 on a
chosen dword in/past ath_softc (function-pointer table entries, sc_cabq,
sc_bslot[], or past-object heap) -> misdirected indirect call / corrupted
queue pointer -> kernel code execution or panic. Requires a HAL (or
misbehaving NIC firmware) that forwards an unbounded antenna byte β the
documented AR9285-class erratum precedent exists on the RX side.
Root cause
sys/dev/netif/ath/ath/if_ath.c:4197-4199 β hardware-fed uint8_t used as array index into two [8] arrays with no clamp, unlike the RX path at sys/dev/netif/ath/ath/if_ath_rx.c:828-835 which clamps for exactly this documented reason.
Fix
Driver-side clamp mirroring the RX path (fix.diff). Validated as a compile sanity check only (no hardware to run): see fix_status=not_testable.
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.diff (git-apply clean) adds the RX-equivalent clamp; compile-checked by construction against the same tree, but no ath NIC exists on the guest to run a baseline/patched comparison
findings/poc/DF-3615/fix.diff; git apply --check output in session log
Confirmed kernel references
Detail
Evidence (decisive lines)
offsets_dmesg.txt + run.log (OFF lines: sizeof=13392, ast_ant_tx=7820, sc_rx=8552, sc_tx=8736, sc_ant_tx=12832, sc_cabq=12864, reach=13852>13392); env.txt (no ath PCI dev, no dev.ath sysctl, if_ath.ko present); if_ath_rx.c:828 clamp precedent; HAL survey in VERDICT.md
PoC changes
n/a - no seeded PoC; authored a KLD offset-printer (offmod.c) to convert the source-ordering claim into measured offsets
Verified recommended fix
Clamp ts_antenna to <=7 in ath_tx_update_stats mirroring the RX-side clamp at if_ath_rx.c:828
Verdict
Missing driver-side clamp on hardware-fed ts->ts_antenna (uint8_t, ah_desc.h:56) used to index ast_ant_tx[8] and sc_ant_tx[8] at if_ath.c:4198-4199 on every successful TX completion. The RX twin clamps the same-class field because AR9285 NICs shipped 'bogusly large' antenna values and crashed users (if_ath_rx.c:809-835). Not reproducible on the audit guest: no Atheros hardware (env.txt) and the write sits on the hardware TX-completion interrupt path; additionally every in-tree HAL currently emits a bounded value (0..2), making the bug latent today. Corruption geometry was measured on the guest with a KLD built in the kernel environment: ast_ant_tx@7820 with indices 183-246 landing on the sc_rx/sc_tx function-pointer method tables (8552/8736); sc_ant_tx@12832 with index 8 exactly hitting the sc_cabq pointer (12864) and indices >140 writing past the end of the 13392-byte softc heap allocation. If a HAL ever forwards a raw antenna byte (the documented AR9285-class failure mode), this is a remotely-paced kernel memory-corruption primitive.
No comments yet.