ar5413: flat pd-gain curve makes sizeCurrVpdTable-1/-2 wrap uint32 -> VpdTable_I[ii][0xFFFFFFFF] wild read at row+0x1FFFFFFFE (+8.6 GB)
Summary
A pd-gain curve with all deltas 0 (flat - legal 4-bit values) gives span 0, so the [size-1]/[size-2] subscripts index with uint32 0xFFFFFFFF/0xFFFFFFFE - byte offsets ~8.59 GB past the last-member vpdTable_I row: direct-map garbage read or KVA-hole panic on channel set. Not necessarily malicious - a flat-calibrated legit card hits it every channel set. Harness SIGSEGV si_addr exactly equals row+0x1FFFFFFFE (asserted); clamp>=2 fix validated. Discovered live: the first harness run segfaulted here before DF-4619's overflow could manifest.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4622 Β· 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 | β | 2.4 KB | β raw | |
| README.md | β | 1.9 KB | β raw | |
| fix.diff | β | 739 B | view raw | |
| fix_build.log | β | 140 B | view raw | |
| fix_run.log | β | 1.3 KB | view raw | |
| verdict.json | β | 3.3 KB | view raw |
DF-4622 β ar5413 flat-curve path: sizeCurrVpdTable-1/-2 uint32 wrap β +8.6 GB wild read
What
ar5413getGainBoundariesAndPdadcsForPowers() computes
sizeCurrVpdTable = Pmax_t2[ii] - Pmin_t2[ii] (ar5413.c:501) β the half-dB
span of the current pd gain's power curve. If the EEPROM curve is flat
(all pwr_delta_t2 for the gain are 0) the span is 0; a single delta of 1
gives span 1. Then:
Vpd_step = (uint16_t)(VpdTable_I[ii][sizeCurrVpdTable-1] - /* :508 */
VpdTable_I[ii][sizeCurrVpdTable-2]); /* :509 */
sizeCurrVpdTable is uint32_t: 0-1 wraps to 0xFFFFFFFF and 0-2 to
0xFFFFFFFE β element offsets Γ2 = +0x1FFFFFFFE / +0x1FFFFFFFC bytes
(+8.59 GB) from the vpdTable_I row inside the heap-allocated
struct ar5413State (last member, ar5413.c:55-56).
In the kernel this reads KVA ~8.6 GB above the RF private state: mapped RAM β
garbage Vpd_step (wrong pdadc extrapolation programmed to hardware);
unmapped hole β page fault β panic. It also silently precedes/coexists
with the DF-4619 kk overflow (this read happens inside the same gain loop).
A flat curve is not even necessarily malicious β a marginally-programmed legit card with a single flat pd gain hits this on every channel set.
Reproduce
cd findings/poc/DF-4622 ./build.sh && ./ar5413_harness 3
Expected decisive lines (run.log):
REPRODUCED: SIGSEGV si_addr=0x200402892, expected wild address 0x200402892 (uint32 index wrap: element index 0xFFFFFFFF x sizeof(uint16_t)=2 => +0x1FFFFFFFE bytes = +8.59 GB) MATCH: wild OOB read proven (ar5413.c uint32 counter underflow)
The asserted equality si_addr == &vpdTable_I[0][0] + 0x1FFFFFFFE pins the
fault to exactly this code path.
Fix validation
fix.diff clamps sizeCurrVpdTable to β₯2 before the reads. Patched run:
mode 3 completes with no SIGSEGV (clamp holds; residual in-row stale values
are memory-safe). See fix_run.log.
DF-4622 VERDICT
Finding
Vpd_step = (uint16_t)(VpdTable_I[ii][sizeCurrVpdTable-1] -
VpdTable_I[ii][sizeCurrVpdTable-2]);
at ar5413.c:508-509 inside ar5413getGainBoundariesAndPdadcsForPowers.
sizeCurrVpdTable (uint32_t, :423/:501) is the current pd gain's power span
in half-dB. A flat EEPROM curve (all pwr_delta_t2 0 for the gain β legal
4-bit values from ar2413ReadCalDataset, ah_eeprom_v3.c:449-452) gives span 0;
0-1/0-2 wrap to 0xFFFFFFFF/0xFFFFFFFE β row-relative byte offsets
+0x1FFFFFFFE/+0x1FFFFFFFC β wild read ~8.6 GB above vpdTable_I (last member
of the heap struct ar5413State, ar5413.c:55-56 β reads past the allocation,
far past).
Reproduction (guest)
Mode 3 of the verbatim-extraction harness: 4 gains, all curves flat (pwr_t4 = {124,124,124,124}). First gain hits :508 with span 0:
REPRODUCED: SIGSEGV si_addr=0x200402892, expected wild address 0x200402892 MATCH: wild OOB read proven (ar5413.c uint32 counter underflow)
0x200402892 == &vpdTable_I[0][0] (0x402894) + 0x1FFFFFFFE β exact address
arithmetic asserted in the SIGSEGV handler. (Userspace faults; in-kernel the
direct map usually makes it a garbage read instead, or a panic on a KVA hole.)
Impact
Crafted (or merely flat-calibrated) EEPROM β wild kernel read on every channel
set: panic-class DoS when the wrapped KVA is unmapped, else garbage
Vpd_step β wrong pdadc extrapolation β radio misprogramming. Same
hardware-gated threat model as DF-1766/DF-4619/DF-4620. No exfiltration
channel (value only feeds extrapolation arithmetic and PHY register writes).
Fix validation
fix.diff clamps sizeCurrVpdTable to β₯2 before the :508-509 reads (clamp
comment documents the residual in-row stale-value read for span<2 β
memory-safe, correctness only). Patched harness mode 3 completes with no
SIGSEGV; values derive from in-row data. See fix_run.log --- mode 3.
Note
Distinct trigger and distinct guard from DF-4620 (:528/:532, zero-gain backfill) though the same uint32-underflow class; distinct from known DF-1766 (numChannels==0 in GetLowerUpperIndex).
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 clamps sizeCurrVpdTable to >=2 before the [size-1]/[size-2] reads; baseline faults at the exact wrapped address, patched mode 3 completes with no SIGSEGV (residual stale in-row reads are memory-safe, correctness-only).
fix_run.log '--- mode 3' section (patched): full mode-3 output with no 'REPRODUCED: SIGSEGV' line and normal termination.
Confirmed kernel references
- sys/dev/netif/ath/ath_hal/ar5212/ar5413.c:501
- sys/dev/netif/ath/ath_hal/ar5212/ar5413.c:503
- sys/dev/netif/ath/ath_hal/ar5212/ar5413.c:508
- sys/dev/netif/ath/ath_hal/ar5212/ar5413.c:509
- sys/dev/netif/ath/ath_hal/ar5413.c:55
- sys/dev/netif/ath/ath_hal/ar5413.c:423
- sys/dev/netif/ath/ath_hal/ah_eeprom_v3.c:449
Detail
Exploit chain
flat EEPROM power curve (deltas 0) -> sizeCurrVpdTable=0 at ar5413.c:501 -> :508-509 uint32 index wrap -> kernel read at vpdTable_I row + 0x1FFFFFFFE (past the ar5413State allocation, ~8.6GB away) -> panic (unmapped KVA) or garbage Vpd_step -> wrong pdadc extrapolation programmed to PHY. No exfil; no uid0 route (hardware-gated).
Evidence (decisive lines)
run.log mode 3 lines: 'REPRODUCED: SIGSEGV si_addr=0x200402892, expected wild address 0x200402892' and 'MATCH: wild OOB read proven (ar5413.c uint32 counter underflow)'; fix_run.log '--- mode 3' shows patched build completing with no fault.
PoC changes
no seed PoC; discovered while isolating DF-4619 (the first harness run segfaulted here before the kk overflow could manifest); mode 3 = flat-curve dataset + SIGSEGV handler asserting the exact expected wrapped address
Verified recommended fix
Clamp sizeCurrVpdTable to >= 2 before the VpdTable_I[ii][sizeCurrVpdTable-1/-2] reads (see fix.diff).
Verdict
REPRODUCED on the DragonFly guest (verbatim-extraction harness, mode 3): with an EEPROM-shaped dataset of flat pd-gain curves (all deltas 0 β legal 4-bit EEPROM values), sizeCurrVpdTable==0 makes ar5413.c:508-509 index VpdTable_I[ii][(uint32)0-1] -> element 0xFFFFFFFF -> read at row+0x1FFFFFFFE bytes (+8.59 GB). SIGSEGV handler asserts si_addr == &vpdTable_I[0][0] + 0x1FFFFFFFE exactly (0x200402892) β the fault is pinned to this line. In-kernel: direct-map garbage read or KVA-hole panic on every channel set with such a curve (crafted EEPROM, or a flat-calibrated legit card). Fix validated: clamping sizeCurrVpdTable>=2 removes the fault; patched mode 3 runs to completion.
No comments yet.