ar5413: numPdGainsUsed==0 path β uint32 counter underflows make pPdGainBoundaries[ii-1] / pPDADCValues[kk-1] read at array+0x1FFFFFFFE (+8.6 GB wild reads), plus uninitialized Pmin_t2[0] stack read
Summary
xpd_mask==0 in the raw dataset leaves numPdGainsUsed==0; the boundary/pdadc backfills then execute [ii-1]/[kk-1] with uint32 counters at zero - the index wraps to 0xFFFFFFFF giving element offset +0x1FFFFFFFE (~8.59 GB) wild reads from the caller's stack and the state struct, values programmed into PHY registers; :468 additionally reads uninitialized stack Pmin_t2[0] feeding txPowerIndexOffset. The HALASSERT is a no-op in release. Harness fault-flavor pinned the read (si_addr == base+0x1FFFFFFFE exactly); delivered run shows both values propagate. Early-bail + caller-check fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4620 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| ar5413_harness.c | β | 11.9 KB | view raw | |
| build.sh | β | 66 B | view raw | |
| run.sh | β | 99 B | view raw | |
| build.log | β | 707 B | view raw | |
| run.log | β | 2.4 KB | view raw | |
| env.txt | β | 271 B | view raw | |
| VERDICT.md | β | 3.0 KB | β raw | |
| README.md | β | 2.7 KB | β raw | |
| fix.diff | β | 1.0 KB | view raw | |
| fix_build.log | β | 140 B | view raw | |
| fix_run.log | β | 1.3 KB | view raw | |
| verdict.json | β | 3.9 KB | view raw |
DF-4620 β ar5413 zero-gain path: uint32 index underflows β +8.6 GB wild reads + uninitialized stack read
What
When the EEPROM provides no pd-gain data for the calibration channel
(xpd_mask == 0 β ar2413SetupRawDataset sets every numVpd = 0,
ah_eeprom_v3.c:656-673), ar5413getGainBoundariesAndPdadcsForPowers() runs
with numPdGainsUsed == 0 and:
- ar5413.c:528
pPdGainBoundaries[ii] = pPdGainBoundaries[ii-1];withii == 0.iiis uint32_t βii-1= 0xFFFFFFFF β the array subscript reads atgainBoundaries + 0x1FFFFFFFEbytes (+8.59 GB) β not a 2-byte under-read. The value then fillsgainBoundaries[0..3]and is programmed intoAR_PHY_TPCRG5byar5413SetPowerTable(ar5413.c:639-644). - ar5413.c:532
pPDADCValues[kk] = pPDADCValues[kk-1];withkk == 0(same uint32 wrap from thepcdacTablebase) β the wild value is replicated across the whole 128-entry pdadc table and written to 32 PHY registers (ar5413.c:630-637). - ar5413.c:468
*pMinCalPower = Pmin_t2[0];reads uninitialized stack (Pmin_t2[]is only written inside thenumVpd > 0branch, :435-445); the garbage propagates intoah_txPowerIndexOffset(:623-626).
The HALASSERT(1 <= numPdGainsUsed && ...) at :580 is compiled out in release
builds (AH_ASSERT undefined; HALASSERT is a no-op, ah_internal.h:665-676;
option exists at sys/conf/options:549 but is not in GENERIC), so nothing stops
this path.
In the kernel the +8.6 GB reads land in the direct map: either mapped RAM (garbage value survives and is programmed to hardware) or a KVA hole (kernel panic).
Reproduce
cd findings/poc/DF-4620 ./build.sh && ./ar5413_harness 2 # maps the exact wild pages (kernel-survival emulation) ./ar5413_harness 2 # again: uninit minPower differs run to run
Expected decisive lines (run.log):
gainBoundaries[0] = 0x5a5a (wild read at gb+0x1FFFFFFFE, ar5413.c:528 ...) pcdacTable[0] = 0x5a5a (wild read at pcdacTable+0x1FFFFFFFE, ar5413.c:532 ...) minPower out = 0xd320 (uninitialized Pmin_t2[0] stack read at ar5413.c:468 ...) DF-4620 REPRODUCED: uint32-underflow wild reads at ar5413.c:528/:532 + uninit stack read at :468
(An earlier unmapped variant faulted at exactly gb + 0x1FFFFFFFE
(si_addr 0xa004902c2 == 0x8004902c4 + 0x1FFFFFFFE) β same primitive,
fault flavor.)
Fix validation
fix.diff returns early when numPdGainsUsed == 0 (and makes
ar5413SetPowerTable fail). Patched run: gainBoundaries[0]=0x0000,
pcdacTable[0]=0xaaaa (untouched), minPower=0x7c7c (sentinel intact β the
uninit read never happens). See fix_run.log.
Threat model
Crafted EEPROM (xpd_mask = 0) on AR5413 hardware β wild KVA reads and/or panic on every channel set. No unprivileged-host path.
DF-4620 VERDICT
Finding
numPdGainsUsed == 0 path in ar5413getGainBoundariesAndPdadcsForPowers
(ar5413.c:397-540). Because the backfill loops decrement uint32 counters past
zero, two array subscripts wrap to element index 0xFFFFFFFF and read at
array + 0x1FFFFFFFE bytes (+8.59 GB):
- :528
pPdGainBoundaries[ii-1](ii==0) β from the caller's stack arraygainBoundaries[4](ar5413.c:555, kernel stack ofar5413SetPowerTable); the value is later written toAR_PHY_TPCRG5(:639-644). - :532
pPDADCValues[kk-1](kk==0) β frompriv->pcdacTable(kernel heap); replicated across all 128 pdadc entries and 32 PHY register writes (:630-637).
Plus :468 *pMinCalPower = Pmin_t2[0] β uninitialized kernel-stack read
(Pmin_t2[] only written when numVpd>0, :435-445) feeding
ahp->ah_txPowerIndexOffset (:623-626).
Release kernels reach this: HALASSERT(1 <= numPdGainsUsed && numPdGainsUsed
<= 3) (:580) is a no-op without AH_ASSERT (ah_internal.h:665-676; not in
GENERIC). Trigger: EEPROM with xpd_mask==0 for the mode's raw dataset
(produced exactly by ar2413SetupRawDataset, ah_eeprom_v3.c:643-671 setting
numVpd=0 for all gains) β i.e., crafted EEPROM, or a degenerately-programmed
card.
Reproduction (guest)
Mode 2 of the same verbatim-extraction harness (see DF-4619 pack). The guest
userspace fault flavor was first observed as SIGSEGV/SIGBUS at
si_addr == gb + 0x1FFFFFFFE (0x8004902c4 + 0x1FFFFFFFE = 0xa004902c2,
exact). To emulate the kernel's direct map (where the +8.6 GB read usually
lands on mapped RAM instead of faulting), the harness mmaps the two exact
wild pages seeded with 0x5A5A β the call then COMPLETES and both wild values
are observed propagating into gainBoundaries[0] and pcdacTable[0]:
gainBoundaries[0] = 0x5a5a (ar5413.c:528 β TPCRG5) pcdacTable[0] = 0x5a5a (ar5413.c:532 β pdadc table / PHY regs) minPower out = 0xd320 / 0xd370 / 0xd300 across runs (uninit stack, :468)
Impact
In-kernel: wild read at KVA +8.6 GB β mapped RAM β garbage programmed into radio registers (malfunction); KVA hole β page fault β panic (DoS). The uninit stack read leaks nothing to userspace (values only feed PHY programming). Gated on attacker-supplied EEPROM β physical/supply-chain precondition, same class as DF-1766. Recorded impact: dos.
Fix validation
fix.diff: early return 0 when numPdGainsUsed==0 (after the gain loop,
before :468) + ar5413SetPowerTable returns AH_FALSE on 0 gains. Patched
harness run: no wild-read propagation (gainBoundaries[0]=0x0000,
pcdacTable[0]=0xaaaa untouched) and minPower stays at its 0x7c7c sentinel β
the uninitialized read never executes. See fix_run.log.
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
fixedfix.diff returns early when numPdGainsUsed==0 and makes ar5413SetPowerTable fail; patched run shows no wild-read propagation (0x0000/0xaaaa) and minPower sentinel 0x7c7c preserved (the :468 uninit read never executes).
fix_run.log '--- mode 2' section: gainBoundaries[0]=0x0000, pcdacTable[0]=0xaaaa, minPower=0x7c7c
Confirmed kernel references
- sys/dev/netif/ath/ath_hal/ar5212/ar5413.c:429
- sys/dev/netif/ath/ath_hal/ar5212/ar5413.c:468
- sys/dev/netif/ath/ath_hal/ar5212/ar5413.c:527
- sys/dev/netif/ath/ath_hal/ar5212/ar5413.c:528
- sys/dev/netif/ath/ath_hal/ar5212/ar5413.c:531
- sys/dev/netif/ath/ath_hal/ar5212/ar5413.c:532
- sys/dev/netif/ath/ath_hal/ar5212/ar5413.c:580
- sys/dev/netif/ath/ath_hal/ar5212/ar5413.c:639
- sys/dev/netif/ath/ath_hal/ah_eeprom_v3.c:643
- sys/dev/netif/ath/ath_hal/ah_internal.h:665
Detail
Exploit chain
crafted EEPROM xpd_mask=0 -> numPdGainsUsed==0 at ar5413.c:463 -> :528/:532 uint32 index wrap reads at +0x1FFFFFFFE from kernel-stack array and kernel-heap pcdacTable -> value programmed to AR_PHY_TPCRG5 / replicated to pdadc table + PHY regs; :468 uninit stack read -> ah_txPowerIndexOffset. Panic-or-garbage; no exfil channel, no uid0 route (hardware-gated).
Evidence (decisive lines)
run.log mode 2 lines: 'gainBoundaries[0] = 0x5a5a', 'pcdacTable[0] = 0x5a5a', 'minPower out = 0xd320', 'DF-4620 REPRODUCED: uint32-underflow wild reads at ar5413.c:528/:532 + uninit stack read at :468'; fix_run.log: patched mode 2 shows 0x0000/0xaaaa/0x7c7c (untouched).
PoC changes
no seed PoC; mode 2 of the shared verbatim-extraction harness: wild pages mmap'd at the exact wrapped addresses with 0x5A5A to emulate kernel direct-map survival (fault flavor also captured: si_addr == gb+0x1FFFFFFFE exact); gainBoundaries passed via a heap struct whose pre-array bytes carry a marker
Verified recommended fix
Early-return when numPdGainsUsed==0 before the Pmin_t2[0] read and backfill loops; fail ar5413SetPowerTable on 0 gains (see fix.diff).
Verdict
REPRODUCED on the DragonFly guest (verbatim-extraction harness, mode 2): with an EEPROM-shaped zero-gain dataset (xpd_mask=0 -> all numVpd=0, the exact shape ar2413SetupRawDataset produces) the function's backfill loops index with uint32 counters at 0: pPdGainBoundaries[ii-1] (ar5413.c:528) and pPDADCValues[kk-1] (:532) wrap to element index 0xFFFFFFFF and read at array+0x1FFFFFFFE bytes (+8.59 GB) β proven by SIGSEGV si_addr exactly equal to gb+0x1FFFFFFFE (0xa004902c2) in the fault flavor, and by mapping the exact wild pages (kernel direct-map emulation) and observing both 0x5A5A values propagate into gainBoundaries[0] (-> AR_PHY_TPCRG5) and pcdacTable[0] (-> 32 PHY reg writes). Plus uninitialized-stack read of Pmin_t2[0] at :468 (minPower varies run-to-run: 0xd320/0xd370/0xd300). In-kernel this is a wild KVA read -> panic (KVA hole) or garbage radio programming; HALASSERT(:580) is compiled out in release. Fix validated (early return): no wild reads, no uninit read.
No comments yet.