ar2316: flat EEPROM power curve -> VpdTable_I[ii][sizeCurrVpdTable-1/-2] uint32-wrap wild reads (twin of DF-4622/ar5413)
Summary
A used pdGain with a flat EEPROM curve (all pwr_delta zero) gives sizeCurrVpdTable 0/1, so the [-1]/[-2] subscripts wrap to 0xFFFFFFFF/0xFFFFFFFE - reads at row+0x1FFFFFFFE (+8.59 GB) whose garbage Vpd_step feeds extrapolated PDADC values programmed to the PHY. Harness SIGSEGV si_addr EXACTLY equals row+0x1FFFFFFFE (asserted by handler); clamp>=2 fix validated. Not necessarily malicious - a flat-calibrated legit card hits it.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4650 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 999 B | β raw | |
| VERDICT.md | β | 3.0 KB | β raw | |
| ar2316_harness.c | β | 12.4 KB | view raw | |
| build.sh | β | 191 B | view raw | |
| run.sh | β | 99 B | view raw | |
| build.log | β | 632 B | view raw | |
| run.log | β | 1.4 KB | view raw | |
| run.2.log | β | 1.4 KB | view raw | |
| env.txt | β | 346 B | view raw | |
| fix.diff | β | 777 B | view raw | |
| fix_validate.sh | β | 1.3 KB | view raw | |
| fix_run.log | β | 3.3 KB | view raw |
DF-4650 β ar2316 flat-curve [size-1]/[-2] uint32 wrap (twin of DF-4622/ar5413)
PoC: verbatim-extraction harness, mode 3. Same build as DF-4648 (see that README; self-contained copies here).
Build (guest, root)
sh build.sh # verbatim-extract ar2316.c:251-499 + cc -O2 -Wno-pointer-sign
Run
./ar2316_harness 3
Expected output
sizeof(struct ar2316State)=2008 offsetof(pcdacTable)=72 Bank1Data=328 vpdTable_I=1492 REPRODUCED: SIGSEGV si_addr=0x200402872, expected wild address 0x200402872 MATCH: wild OOB read proven (ar2316.c uint32 counter underflow)
Exit 0 = reproduced (si_addr exactly equals &vpdTable_I[0][0] +
0x1FFFFFFFE). With fix.diff (sizeCurrVpdTable clamped β₯2) mode 3
completes without faulting and prints sane values.
Threat model
Crafted EEPROM on an AR2316 card: a used pdGain with all-zero pwr_delta_t2 (flat curve) makes sizeCurrVpdTable==0 β VpdTable_I[ii][-1]/[-2] wrap to a +8.59 GB wild read at ar2316.c:467-468.
DF-4650 β ar2316: flat EEPROM power curve β VpdTable_I[ii][sizeCurrVpdTable-1/-2] uint32-wrap wild reads (twin of DF-4622/ar5413)
Verdict: REPRODUCED (harness, verbatim kernel code, exact-address SIGSEGV) β impact: dos (hardware-gated)
Root cause
sys/dev/netif/ath/ath_hal/ar5212/ar2316.c β
ar2316getGainBoundariesAndPdadcsForPowers():
460: sizeCurrVpdTable = Pmax_t2[ii] - Pmin_t2[ii]; /* uint32_t */
461: tgtIndex = pPdGainBoundaries[ii] + pdGainOverlap_t2 - Pmin_t2[ii];
...
467: Vpd_step = (uint16_t)(VpdTable_I[ii][sizeCurrVpdTable-1] -
468: VpdTable_I[ii][sizeCurrVpdTable-2]);
Pmax_t2/Pmin_t2 are halves of the EEPROM pwr_t4 curve endpoints
(ar2316.c:394-404). If the curve for a used pdGain is flat (all
EEPROM pwr_delta_t2 deltas zero β pwr_t4[numVpd-1] == pwr_t4[0]),
then sizeCurrVpdTable == 0 and sizeCurrVpdTable-1 / -2 wrap the
uint32_t to 0xFFFFFFFF / 0xFFFFFFFE β reads at
VpdTable_I[ii] + 0x1FFFFFFFE bytes (+8.59 GB wild read). With
sizeCurrVpdTable == 1 (single step) the -2 term wraps. Nothing
guards the subtraction; the neighboring unconditional read
VpdTable_I[ii][1] - VpdTable_I[ii][0] (ar2316.c:449) stays in-row and
is not itself OOB.
In the kernel the wild read lands in the direct map (garbage Vpd_step
β garbage extrapolated PDADC values programmed to the radio) or in a KVA
hole β panic.
Trigger (threat model)
Crafted EEPROM on an AR2316-bearing card: any used pdGain whose
pwr_delta_t2[kk] entries are all zero for the channel pair bracketing
the target channel (ar2413EepromToRawDataset then yields
pwr_t4 = {v,v,v,v}, ah_eeprom_v3.c:715-719).
How it was verified
Verbatim-extraction harness (ar2316_harness.c mode 3): four used
gains with flat curves (pwr_t4[0]=124, step 0). The wild address is left
unmapped so the wrap faults; the SIGSEGV handler asserts
si_addr == &vpdTable_I[0][0] + 0x1FFFFFFFE exactly.
run.log (decisive lines):
REPRODUCED: SIGSEGV si_addr=0x200402872, expected wild address 0x200402872 MATCH: wild OOB read proven (ar2316.c uint32 counter underflow)
The exact-address match proves the read is the [sizeCurrVpdTable-1]
uint32 wrap (element index 0xFFFFFFFF Γ 2 bytes), not any other access.
Exploitation ceiling
Hardware-gated (crafted EEPROM). Wild KVA read β panic or garbage radio programming. No exfiltration channel, no uid0 route.
Fix validation
fix.diff (mirrors the accepted DF-4622 fix): clamp
sizeCurrVpdTable to β₯2 before the reads. fix_run.log (PATCHED mode
3): completes normally (no SIGSEGV), minPower=0x003e β a sane value
derived from the dataset.
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 (in-row reads); on the patched extraction mode 3 completes with no SIGSEGV and sane outputs (minPower=0x003e). Harness-level validation of the patched function bodies; kernel-build validation hardware-gated (no AR2316 on guest).
fix_run.log '--- PATCHED mode 3' section: completes, 'mode=3 numPdGainsUsed(ret)=4 minPower=0x003e', no fault
Confirmed kernel references
- sys/dev/netif/ath/ath_hal/ar5212/ar2316.c:394
- sys/dev/netif/ath/ath_hal/ar5212/ar2316.c:400
- sys/dev/netif/ath/ath_hal/ar5212/ar2316.c:449
- sys/dev/netif/ath/ath_hal/ar5212/ar2316.c:460
- sys/dev/netif/ath/ath_hal/ar5212/ar2316.c:467
- sys/dev/netif/ath/ath_hal/ar5212/ar2316.c:474
- sys/dev/netif/ath/ath_hal/ah_eeprom_v3.c:715
- sys/dev/netif/ath/ath_hal/ar5212/ar2316.c:41
Detail
Exploit chain
crafted EEPROM flat curve (all pwr_delta_t2=0 for a used pdGain) -> sizeCurrVpdTable==0 at ar2316.c:460 -> :467-468 uint32 index wrap -> wild KVA read at vpdTable row +0x1FFFFFFFE -> panic (KVA hole) or garbage Vpd_step -> garbage PDADC extrapolation programmed to the PHY.
Evidence (decisive lines)
run.log mode 3: 'REPRODUCED: SIGSEGV si_addr=0x200402872, expected wild address 0x200402872' + 'MATCH: wild OOB read proven (ar2316.c uint32 counter underflow)'; run.2.log identical; fix_run.log PATCHED mode 3: completes, minPower=0x003e (sane), no fault.
PoC changes
no seed PoC; mode 3 of the shared verbatim-extraction harness (same build.sh as DF-4648): flat-curve dataset crafted so sizeCurrVpdTable==0 for every gain; wild address left unmapped so the wrap faults, with the SIGSEGV handler asserting the exact expected wild address.
Verified recommended fix
Clamp sizeCurrVpdTable to >=2 before the [size-1]/[size-2] reads at ar2316.c:467-468 (see fix.diff).
Verdict
REPRODUCED on the DragonFly guest (verbatim-extraction harness, mode 3): with four used pdGains carrying FLAT EEPROM curves (all pwr_delta_t2 zero -> pwr_t4={124,124,124,124} -> Pmax_t2==Pmin_t2 -> sizeCurrVpdTable==0) the reads VpdTable_I[ii][sizeCurrVpdTable-1/-2] at ar2316.c:467-468 wrap the uint32 index to 0xFFFFFFFF/0xFFFFFFFE and read at row +0x1FFFFFFFE bytes (+8.59 GB): SIGSEGV with si_addr EXACTLY equal to &vpdTable_I[0][0]+0x1FFFFFFFE (0x200402872), asserted in the handler. In-kernel the same wild read lands in the direct map (garbage Vpd_step -> garbage extrapolated PDADC values programmed to the radio via ar2316.c:474-482/:588-595) or in a KVA hole -> panic. sizeCurrVpdTable==1 (single-step curve) wraps the -2 term identically. Hardware-gated (crafted EEPROM on an AR2316 card); no exfil channel, no uid0 route. Fix validated: clamping sizeCurrVpdTable to >=2 makes mode 3 complete without faulting.
No comments yet.