ar2413: flat EEPROM power curve β VpdTable_I[ii][sizeCurrVpdTable-1/-2] uint32-wrap wild reads at row+0x1FFFFFFFE (twin of DF-4622/ar5413, DF-4650/ar2316)
Summary
A flat pd-gain curve (all deltas 0, legal 4-bit values) makes the span 0/1, so the [-1]/[-2] subscripts wrap uint32 - reads at row+0x1FFFFFFFE whose garbage Vpd_step feeds extrapolated PDADC values programmed to the radio. Harness SIGSEGV si_addr EXACTLY equals row+0x1FFFFFFFE (asserted). Clamp>=2 fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4662 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.4 KB | β raw | |
| VERDICT.md | β | 2.3 KB | β raw | |
| ar2413_harness.c | β | 12.4 KB | view raw | |
| build.sh | β | 85 B | view raw | |
| run.sh | β | 99 B | view raw | |
| fix_validate.sh | β | 1.3 KB | view raw | |
| fix.diff | β | 757 B | view raw | |
| build.log | β | 632 B | view raw | |
| run.log | β | 2.0 KB | view raw | |
| fix_run.log | β | 3.1 KB | view raw | |
| env.txt | β | 946 B | view raw | |
| manifest.json | β | 839 B | view raw | |
| verdict.json | β | 3.3 KB | view raw |
DF-4662 β ar2413: flat EEPROM power curve β VpdTable_I[ii][sizeCurrVpdTable-1/-2] wild reads
Twin of DF-4622 (ar5413) / DF-4650 (ar2316). If a pd gain's EEPROM curve is
flat (all pwr deltas 0 β pwr_t4 = {124,124,124,124} β Pmax_t2 == Pmin_t2)
then sizeCurrVpdTable = Pmax_t2[ii] - Pmin_t2[ii] is 0 (ar2413.c:457) and
Vpd_step = (uint16_t)(VpdTable_I[ii][sizeCurrVpdTable-1] - /* ar2413.c:464 */
VpdTable_I[ii][sizeCurrVpdTable-2]); /* ar2413.c:465 */
wraps the uint32 index to 0xFFFFFFFF / 0xFFFFFFFE β reads at
row + 0x1FFFFFFFE bytes (+8.59 GB). A single-step curve
(sizeCurrVpdTable==1) wraps the -2 term identically. All-legal 4-bit
EEPROM values; HALASSERT is compiled out in release.
Build
On the DragonFly guest: cd /root/df2413 && sh build.sh (shared harness,
see DF-4660 pack).
Run
./ar2413_harness 3 # mode 3 = this finding
Expected
REPRODUCED: SIGSEGV si_addr=0x20040286a, expected wild address 0x20040286a (uint32 index wrap: element index 0xFFFFFFFF x sizeof(uint16_t)=2 => +0x1FFFFFFFE bytes = +8.59 GB) MATCH: wild OOB read proven (ar2413.c uint32 counter underflow)
The SIGSEGV handler asserts si_addr == &vpdTable_I[0][0] + 0x1FFFFFFFE
exactly β pinning the fault to ar2413.c:464.
Fix validation
sh fix_validate.sh (fixA+B+C): with sizeCurrVpdTable clamped to β₯2,
patched mode 3 completes with no fault (garbage-but-in-bounds Vpd_step).
DF-4662 β VERDICT
REPRODUCED (impact: dos β wild KVA read on every channel set with a flat curve; hardware-gated trigger)
Method
Same verbatim-extraction harness as DF-4660 (real ar2413.c:245-496 bodies,
faithful struct ar2413State). Mode 3 crafts four used pd gains with FLAT
EEPROM curves β all pwr_delta_t2 zero is a fully legal EEPROM shape β so
Pmax_t2 == Pmin_t2 == 62 and sizeCurrVpdTable == 0 for every gain
(ar2413.c:457).
Result (run.log, stable across two baseline runs)
The reads at ar2413.c:464-465 index VpdTable_I[ii][(uint32)0-1] /
[(uint32)0-2] β element indices 0xFFFFFFFF / 0xFFFFFFFE β byte offsets
+0x1FFFFFFFE / +0x1FFFFFFFC from the row base. Left unmapped, the process
takes SIGSEGV with si_addr exactly equal to
&vpdTable_I[0][0] + 0x1FFFFFFFE (0x20040286a), asserted inside the
handler β the fault is pinned to this line, not incidental:
REPRODUCED: SIGSEGV si_addr=0x20040286a, expected wild address 0x20040286a MATCH: wild OOB read proven (ar2413.c uint32 counter underflow)
In the kernel the same address lands in the direct map (garbage Vpd_step
feeds the extrapolated PDADC values programmed to the radio at
ar2413.c:471-479 β ar2413.c:586-593) or in a KVA hole β panic on every
channel change with such a curve (crafted EEPROM, or a flat-calibrated
legitimate card). sizeCurrVpdTable==1 (single-step curve) wraps the -2
term identically.
Exploitation ceiling
Crafted EEPROM on AR2413 hardware; wild read β panic or garbage radio programming. No write primitive, no exfil, no uid0 route.
Fix validation (fix_run.log)
fix.diff clamps sizeCurrVpdTable to β₯2 before the reads (keeps them
in-row; Vpd_step then derives from in-bounds cells). Patched mode 3
completes without faulting. git-apply verified against the pristine tree.
NOT re-reported
DF-1796 (numChannels==0) and DF-4661 (numPdGainsUsed==0 β same uint32-underflow class, different trigger and guard) are distinct.
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
fixedValidated on the guest: fixA+B+C applied to a copy of the pristine ar2413.c, function bodies re-extracted verbatim from the patched copy, harness rebuilt and re-run. Baseline reproduces the defect; patched build shows the defect GONE (mode 1: no overflow / mode 2: early return, sentinel minPower, no wild-read propagation / mode 3: completes without the wild-address SIGSEGV). fix.diff is git-apply-able against the pristine tree (git apply --check clean) and was never applied to sys/.
fix_run.log (full patched 3-mode run) vs run.log (baseline); fix.diff in pack; PATCHES_APPLIED + FIXED_BUILD_OK in fix_run.log.
Confirmed kernel references
Detail
Exploit chain
flat-calibrated/crafted v3 EEPROM curve on AR2413 card -> sizeCurrVpdTable==0 -> uint32-wrap wild read at vpdTable_I row+0x1FFFFFFFE on every channel set -> KVA-hole panic (or direct-map garbage -> radio misprogramming). No uid0 route.
Evidence (decisive lines)
run.log mode 3: 'REPRODUCED: SIGSEGV si_addr=0x20040286a, expected wild address 0x20040286a / MATCH'; repeat section same address; fix_run.log patched mode 3: completes, no fault.
PoC changes
Same harness as DF-4660 (mode 3); no kernel-code changes.
Verified recommended fix
Clamp sizeCurrVpdTable to >=2 before the VpdTable_I[ii][sizeCurrVpdTable-1/-2] reads (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==62 -> sizeCurrVpdTable==0) the reads VpdTable_I[ii][sizeCurrVpdTable-1/-2] at ar2413.c:464-465 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 (0x20040286a), asserted in the handler; stable across two baseline runs. In-kernel the same wild read lands in the direct map (garbage Vpd_step -> garbage extrapolated PDADC values programmed to the radio via ar2413.c:471-479/:586-593) or in a KVA hole -> panic. sizeCurrVpdTable==1 (single-step curve) wraps the -2 term identically. Twin of DF-4622 (ar5413) / DF-4650 (ar2316). Hardware-gated (crafted EEPROM on an AR2413 card); no exfil channel, no uid0 route. Fix validated: clamping sizeCurrVpdTable to >=2 makes mode 3 complete without faulting.
No comments yet.