ar2425: FillVpdTable/interpolate span never clamped — descending curve → (uint16_t)(Pmax−Pmin) wrap → ~131 KB sequential OOB write past the function-static VpdTable_* block in kernel static storage; valid curve spanning >63 half-dB → OOB write with no wrap (ar2425 twin of DF-4683, flagged at ar2425.c:311)
Summary
Neither loop clamps the span to the 64-wide row or rejects Pmax<Pmin. ar2425 twist: the scratch is FUNCTION-SCOPE STATICS (.bss) - the descending-curve wrap marches ~128KB through the whole statics block into adjacent kernel statics; a valid ascending curve >63 half-dB spills rows and escapes with no wrap. REACHABILITY refinement: the v3/v5 EEPROM decoder uses unsigned 4-bit cumulative deltas, so legal pier curves are non-descending with span<=60 - latent today (decoder change or divergent caller re-arms it); filed per the DF-4683 family-mirror directive. Harness modes 4/5 fault exactly at the block end (static-storage emulation). Skip-inverted/oversized-spans fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4686 · 16 files| File | Type | Description | Size | |
|---|---|---|---|---|
| ar2425_harness.c | — | 19.2 KB | view raw | |
| build.sh | — | 85 B | view raw | |
| run.sh | — | 109 B | view raw | |
| fixA.diff | — | 1.2 KB | view raw | |
| fixB.diff | — | 921 B | view raw | |
| fixC.diff | — | 1.2 KB | view raw | |
| fixD.diff | — | 1009 B | view raw | |
| fix_validate.sh | — | 3.3 KB | view raw | |
| build.log | — | 773 B | view raw | |
| run.log | — | 3.8 KB | view raw | |
| fix_run.log | — | 10.2 KB | view raw | |
| env.txt | — | 435 B | view raw | |
| fix.diff | — | 1009 B | view raw | |
| VERDICT.md | — | 3.9 KB | ↓ raw | |
| manifest.json | — | 1019 B | view raw | |
| verdict.json | — | 5.1 KB | view raw |
DF-4686 -- verdict narrative
What was claimed
FillVpdTable/interpolate span never clamped (ar2425 twin of DF-4683): descending curve -> (uint16_t)(Pmax-Pmin) wrap -> ~131 KB sequential OOB write past the function-static VpdTable_* block in kernel static storage; valid curve spanning >63 half-dB -> OOB write past the block with NO wrap
Reproduced?
YES (mode 4 and 5 of the shared verbatim-extraction harness, run on the DragonFly guest).
REPRODUCED on the DragonFly guest (verbatim-extraction harness, modes 4 and 5) -- the ar2425 twin of DF-4683 with an ar2425-specific twist: ar2425.c keeps VpdTable_L/R/I + Pmin_t2/Pmax_t2 as FUNCTION-SCOPE STATICS (ar2425.c:367-379), not struct members like the ar2317/ar2316/ar2413/ar5413 twins, so the span overflow marches through KERNEL STATIC STORAGE (adjacent .bss symbols), not a heap object. Neither ar2425FillVpdTable's loop (bound = (uint16_t)(Pmax-Pmin) at ar2425.c:311, store at :325) nor the interpolate loop (:419-424) clamps the span to the 64-wide row or rejects Pmax<Pmin. (Mode 4) a DESCENDING pwr_t4 curve yields Pmax_t2=84 < Pmin_t2=90 (the :398-408 min() across idxL/idxR performs no monotonicity check), the bound wraps to 65530 -> 131060 bytes of uint16 stores from VpdTable_L[0][0] through the whole 1552-byte statics block and off its end; the harness faulted on the STORE at exactly the block end (guard page, exact match). (Mode 5) a VALID ascending curve spanning 102 half-dB needs no wrap: FillVpdTable spills rows L[3]/R[3][64..] into neighboring statics, and the interpolate loop writes VpdTable_I[3][0..101]; I[3][64] lands on Pmin_t2 (kernel: adjacent .bss), clobbers the loop's own re-evaluated bound to a uint16-wrapped ~65535 and the march continues off the block end (fault exact-match). Values written are EEPROM-Vpd-derived. REACHABILITY (honest, refines DF-4683's framing): through the ONLY constructor (readEepromRawPowerCalInfo2413 -> ar2413EepromToRawDataset, ah_eeprom_v3.c:703-747) pwr deltas are UNSIGNED 4-bit fields (dbm_delta_mask=0xF, ah_eeprom_v3.c:450) summed cumulatively, so every pier curve is provably non-descending with span <= 4x15=60 half-dB: neither variant is constructible from EEPROM bits TODAY -- the missing clamp is latent/defense-in-depth (any decoder change, a divergent caller, or an eeprom v>5 re-arm it). Filed as the family twin DF-4683 explicitly flagged at ar2425.c:311. Fix validated: fixD (reject inverted/oversized spans) alone completes mode 5; mode 4 completes with fixD+fixB (skipping the inverted gain leaves zero gains -- DF-4688's early return covers the residual).
Fix validation
Harness-level validation (fix_validate.sh): baseline modes 4/5 fault at the statics-block guard on the unpatched build; with fixD applied, mode 5 completes; mode 4 completes with fixD+fixB (residual zero-gain path after skipping the inverted gain). No kernel rebuild: no AR2425 hardware on the guest, the fix is validated against the verbatim-extracted function bodies per the family methodology (DF-4619..DF-4683).
Decisive evidence: run.log: 'mode 4 ... si_addr is 1552 bytes past block start (sizeof=1552) ... MATCH' and 'mode 5 ... MATCH'; fix_run.log: 'PATCHED mode 5 with fixD only ... no SIGSEGV' and 'PATCHED mode 4 with fixB+fixD ... no SIGSEGV'.
Why no in-kernel run
The guest has no AR2425 NIC and no EEPROM-injection path; the family methodology (DF-4619..DF-4683, all accepted with the same constraint) compiles the real function bodies against faithful struct/static layouts and proves the violation with exact-address faults / canaries / propagation -- equivalent strength for a hardware-gated defect, and it validates the fix the same way.
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
fixedHarness-level validation (fix_validate.sh): baseline modes 4/5 fault at the statics-block guard on the unpatched build; with fixD applied, mode 5 completes; mode 4 completes with fixD+fixB (residual zero-gain path after skipping the inverted gain). No kernel rebuild: no AR2425 hardware on the guest, the fix is validated against the verbatim-extracted function bodies per the family methodology (DF-4619..DF-4683).
run.log: 'mode 4 ... si_addr is 1552 bytes past block start (sizeof=1552) ... MATCH' and 'mode 5 ... MATCH'; fix_run.log: 'PATCHED mode 5 with fixD only ... no SIGSEGV' and 'PATCHED mode 4 with fixB+fixD ... no SIGSEGV'.
Confirmed kernel references
- sys/dev/netif/ath/ath_hal/ar5212/ar2425.c:311
- sys/dev/netif/ath/ath_hal/ar5212/ar2425.c:325
- sys/dev/netif/ath/ath_hal/ar5212/ar2425.c:419
- sys/dev/netif/ath/ath_hal/ar5212/ar2425.c:367
- sys/dev/netif/ath/ath_hal/ar5212/ar2425.c:373
- sys/dev/netif/ath/ath_hal/ah_eeprom_v3.h:291
- sys/dev/netif/ath/ath_hal/ah_eeprom_v3.c:450
- sys/dev/netif/ath/ath_hal/ah_eeprom_v3.c:703
Detail
Exploit chain
crafted-EEPROM AR2425 card -> ath attach -> ar2425SetPowerTable (channel set/reset) -> ar2425getGainBoundariesAndPdadcsForPowers -> OOB write/wild read; ceiling radio misprogramming/panic (DoS), not uid0
Evidence (decisive lines)
run.log: 'mode 4 ... si_addr is 1552 bytes past block start (sizeof=1552) ... MATCH' and 'mode 5 ... MATCH'; fix_run.log: 'PATCHED mode 5 with fixD only ... no SIGSEGV' and 'PATCHED mode 4 with fixB+fixD ... no SIGSEGV'.
PoC changes
ar2425-specific harness (vs DF-4680 template): the 5 function-scope static declarations (ar2425.c:367-379) are stripped from the verbatim extraction (asserted count) and re-bound to a guarded mmap block emulating kernel static storage -- ar2425 keeps the VpdTable/Pmin/Pmax scratch in .bss, not in ar2425State like the family twins; first build attempt had a macro/field name collision (offsetof) fixed by m_-prefixing the emulation fields; guest tar transfer ordering bug fixed (extract before overwrite)
Verified recommended fix
Reject inverted (Pmax
Verdict
REPRODUCED on the DragonFly guest (verbatim-extraction harness, modes 4 and 5) -- the ar2425 twin of DF-4683 with an ar2425-specific twist: ar2425.c keeps VpdTable_L/R/I + Pmin_t2/Pmax_t2 as FUNCTION-SCOPE STATICS (ar2425.c:367-379), not struct members like the ar2317/ar2316/ar2413/ar5413 twins, so the span overflow marches through KERNEL STATIC STORAGE (adjacent .bss symbols), not a heap object. Neither ar2425FillVpdTable's loop (bound = (uint16_t)(Pmax-Pmin) at ar2425.c:311, store at :325) nor the interpolate loop (:419-424) clamps the span to the 64-wide row or rejects Pmax<Pmin. (Mode 4) a DESCENDING pwr_t4 curve yields Pmax_t2=84 < Pmin_t2=90 (the :398-408 min() across idxL/idxR performs no monotonicity check), the bound wraps to 65530 -> 131060 bytes of uint16 stores from VpdTable_L[0][0] through the whole 1552-byte statics block and off its end; the harness faulted on the STORE at exactly the block end (guard page, exact match). (Mode 5) a VALID ascending curve spanning 102 half-dB needs no wrap: FillVpdTable spills rows L[3]/R[3][64..] into neighboring statics, and the interpolate loop writes VpdTable_I[3][0..101]; I[3][64] lands on Pmin_t2 (kernel: adjacent .bss), clobbers the loop's own re-evaluated bound to a uint16-wrapped ~65535 and the march continues off the block end (fault exact-match). Values written are EEPROM-Vpd-derived. REACHABILITY (honest, refines DF-4683's framing): through the ONLY constructor (readEepromRawPowerCalInfo2413 -> ar2413EepromToRawDataset, ah_eeprom_v3.c:703-747) pwr deltas are UNSIGNED 4-bit fields (dbm_delta_mask=0xF, ah_eeprom_v3.c:450) summed cumulatively, so every pier curve is provably non-descending with span <= 4x15=60 half-dB: neither variant is constructible from EEPROM bits TODAY -- the missing clamp is latent/defense-in-depth (any decoder change, a divergent caller, or an eeprom v>5 re-arm it). Filed as the family twin DF-4683 explicitly flagged at ar2425.c:311. Fix validated: fixD (reject inverted/oversized spans) alone completes mode 5; mode 4 completes with fixD+fixB (skipping the inverted gain leaves zero gains -- DF-4688's early return covers the residual).
No comments yet.