FillVpdTable/interpolate span never clamped: descending EEPROM curve β (uint16_t)(Pmax-Pmin) wrap β ~131 KB sequential OOB heap WRITE past ar2317State; valid curve spanning >63 half-dB β OOB write past object end with NO wrap (not covered by any family twin)
Summary
Neither the fill loop nor the interpolate loop clamps the span to the 64-wide row or rejects Pmax<Pmin. A descending curve wraps the bound to ~65530 -> up to 131 KB of uint16 stores marching through and past the 2008-byte object into adjacent heap (semi-controlled EEPROM-derived data); a VALID ascending curve spanning 102 half-dB overflows past the last member with no wrap at all. Present verbatim in ar2413/ar2316/ar5413/ar2425 - first filed here. Harness: both modes fault exactly at the struct end; fix (skip inverted/oversized spans) validated. Family-mirror recommended.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4683 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.6 KB | β raw | |
| VERDICT.md | β | 4.1 KB | β raw | |
| ar2317_harness.c | β | 16.5 KB | view raw | |
| build.sh | β | 85 B | view raw | |
| run.sh | β | 121 B | view raw | |
| fix_validate.sh | β | 2.8 KB | view raw | |
| fix.diff | β | 881 B | view raw | |
| fixB.diff | β | 699 B | view raw | |
| build.log | β | 686 B | view raw | |
| run.log | β | 3.0 KB | view raw | |
| fix_run.log | β | 8.6 KB | view raw | |
| env.txt | β | 844 B | view raw | |
| manifest.json | β | 1.1 KB | view raw | |
| verdict.json | β | 5.7 KB | view raw |
DF-4683 β ar2317: FillVpdTable/interpolate span never clamped β OOB heap WRITE
past ar2317State (uint16 wrap runaway + valid-oversized-span variants)
NEW (not covered by the ar5413/ar2316/ar2417/ar2413 family twins): neither
ar2317FillVpdTable() nor the interpolate loop bounds the pd-gain power span
to the vpdTable row width (MAX_PWR_RANGE_IN_HALF_DB = 64,
ah_eeprom_v3.h:291) or validates Pmax >= Pmin.
Build
Same harness as DF-4680: cd /root/df2317 && sh build.sh
(the ar2317State object is placed with its end exactly at a page boundary and
a PROT_NONE guard page after it, emulating the kernel heap allocation)
Run
./ar2317_harness 4 # descending EEPROM curve -> uint16 wrap runaway ./ar2317_harness 5 # valid curve spanning 102 -> plain missing clamp
Expected (baseline)
--- mode 4 --- REPRODUCED: SIGSEGV si_addr=0x800476000 object guard page at 0x800476000 (struct end); si_addr is 2008 bytes past struct start (sizeof=2008) FillVpdTable loop bound (uint16_t)(Pmax-Pmin) = (uint16_t)(84-90) = 65530 entries (131060 bytes) into a 64-wide row MATCH: unbounded OOB WRITE ran off the end of the ar2317State heap object --- mode 5 --- (identical guard-page fault, from VALID data, no wrap)
- Mode 4:
Pmin_t2/Pmax_t2are computed at ar2317.c:372-382 without any monotonicity check; a descending pwr_t4 list givesPmax < Pmin, sowhile (ii <= (uint16_t)(Pmax - Pmin))(ar2317.c:289) wraps to ~65530 and the loop writes up to 131060 bytes fromvpdTable_L[0][0]β through the whole 2008-byte object and off its end (kernel: adjacent heap objects corrupted / panic). - Mode 5: a valid ascending curve spanning 102 half-dB needs no wrap:
FillVpdTable writes row 3 indices 64..102 (intra-object spill) and the
interpolate loop (ar2317.c:393-398) writes
VpdTable_I[3][0..101]β vpdTable_I is the last member, so indices β₯64 land past the object.
Written values are EEPROM-Vpd-derived (0..255, or 0xFFxx on negative extrapolation): semi-controlled kernel-heap overwrite. Rated Medium (the twins are Low) for the beyond-object write reach.
Fix validation
fix.diff (fixD) skips any pdGain with Pmax < Pmin or span β₯
MAX_PWR_RANGE_IN_HALF_DB (continue before the FillVpdTable/interpolate
calls). Patched: mode 5 completes with fixD alone (numPdGainsUsed=3); mode 4
completes with fixD+fixB (fixB.diff = DF-4681's early return; skipping the
inverted gain leaves zero gains, whose residual path DF-4681's fix covers).
The same defect exists verbatim in ar2413.c:309, ar2316.c:312, ar5413.c:353, ar2425.c:311 β recommend fixing all five together.
DF-4683 β VERDICT
Status: REPRODUCED (guest, verbatim-extraction harness, modes 4+5) Β· impact: dos (kernel heap corruption) Β· confidence: certain Β· fix: fixed (validated)
Root cause
Two loops write into priv->vpdTable_{L,R,I}[pdGainIdx][ii] β rows are
MAX_PWR_RANGE_IN_HALF_DB = 64 uint16 entries (ah_eeprom_v3.h:291) inside
the heap-allocated struct ar2317State (last three members; vpdTable_I is
the final member):
ar2317FillVpdTable()βwhile (ii <= (uint16_t)(Pmax - Pmin))retVpdList[pdGainIdx][ii] = kk;(ar2317.c:289-306). The only sanity check inside isnumIntercepts < 2(:286); the spanPmax - Pminis never validated against the row width, andPmax < Pminis never rejected.- the interpolate loop β
for (kk = 0; kk < (uint16_t)(Pmax_t2[..] - Pmin_t2[..]); kk++) VpdTable_I[numPdGainsUsed][kk] = ...(ar2317.c:393-398), same missing clamp.
Pmin_t2/Pmax_t2 derive straight from EEPROM bytes at :372-382
(pwr_t4 quarters of dBm, /2 β half dB): descending lists give
Pmax < Pmin (wrap trigger), wide lists give spans up to 127 (> 64; plain
clamp trigger). No sorting/monotonicity validation exists anywhere on the
v3-EEPROM raw-data path.
Verified behavior (run.log)
The ar2317State is emulated as the kernel heap allocation: mmap'd, struct end placed exactly at a page boundary, PROT_NONE guard page after.
- Mode 4 (wrap) β gain slot 3 with pwr_t4 {180,176,172,168} β
Pmin_t2=90, Pmax_t2=84 β
(uint16_t)(84-90) = 65530β the loop starts storing atvpdTable_L[0][0]and marches; SIGSEGV on the STORE at exactly the struct end (si_addr == guard base; the faulting instruction is theretVpdList[...] = kkstore β the reads at that point target the mapped dataset). Proven escape of a would-be 131060-byte sequential overwrite with Vpd-derived values. - Mode 5 (clamp, no wrap) β gain slot 0 with pwr_t4 {0,68,136,204}
(perfectly valid ascending data, span 102): FillVpdTable row-3 indices
64..102 spill intra-object (vpdTable_R/vpdTable_I heads β observed canary
clobber), then the interpolate loop writes
VpdTable_I[3][64..101], i.e. past the object end; SIGSEGV at exactly&vpdTable_I[3][64]== struct end. Valid EEPROM data, no integer wrap, pure missing clamp.
In-kernel: writes continue into adjacent kernel heap objects
(ath_hal_malloc = kmalloc) for up to ~128 KB (mode 4) β adjacent-object
corruption with semi-controlled content, or immediate panic on unmapped KVA.
This is an OOB write beyond the heap object, a class stronger than the
intra-object spill (DF-4680) and wild reads (DF-4681/4682) filed for the
family β hence Medium, memcorrupt bucket.
Why the family twins did not cover it
DF-4619/4620/4622 (ar5413), DF-4648/4649/4650 (ar2316), DF-4660/4661/4662
(ar2413) each filed the three specified twins only. The span defect is
verbatim-present in all five ar5212 RF files (ar2317.c:289 == ar2413.c:309
== ar2316.c:312 == ar5413.c:353 == ar2425.c:311); this is its first filing
(on ar2317, as instructed). Recommend mirroring the fix to the other four.
Fix validation (fix_run.log)
fix.diff (fixD): continue (skip gain) when Pmax_t2 < Pmin_t2 or
(Pmax_t2 - Pmin_t2) >= MAX_PWR_RANGE_IN_HALF_DB, placed after the
Pmin/Pmax computation and before the FillVpdTable/interpolate calls.
- Mode 5 with fixD alone: completes, numPdGainsUsed=3 (oversized gain
skipped), no fault.
- Mode 4 with fixD+fixB (fixB = DF-4681's numPdGainsUsed==0 early return,
included as fixB.diff): completes, ret=0, sentinel minPower β the
residual zero-gain path created by skipping the only gain is covered by
DF-4681's guard, as documented.
git apply --check clean against sys/; never applied to sys/.
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: fix.diff (skip any pdGain whose span is inverted (Pmax
fix_run.log ('===== fixD' and '===== fixD + fixB' sections: PATCHES_APPLIED, FIXED_BUILD_OK, both modes 'no SIGSEGV: function completed'); run.log modes 4/5 for the baseline guard-page faults.
Confirmed kernel references
- sys/dev/netif/ath/ath_hal/ar5212/ar2317.c:289-306
- sys/dev/netif/ath/ath_hal/ar5212/ar2317.c:273-309
- sys/dev/netif/ath/ath_hal/ar5212/ar2317.c:372-382
- sys/dev/netif/ath/ath_hal/ar5212/ar2317.c:393-398
- sys/dev/netif/ath/ath_hal/ar5212/ar2317.c:59-60
- sys/dev/netif/ath/ath_hal/ah_eeprom_v3.h:291
- sys/dev/netif/ath/ath_hal/ah_eeprom_v3.h:294-314
Detail
Exploit chain
crafted v3 EEPROM on AR2317 card: (a) descending pwr_t4 curve -> (uint16_t)(Pmax-Pmin) wrap -> ~131 KB sequential uint16 overwrite from vpdTable_L through and past the ath_hal_malloc'd ar2317State into adjacent kernel heap objects -> corruption/panic; or (b) valid curve spanning >63 half-dB -> up to 128 bytes past the object end via VpdTable_I[3][64..]. Values are Vpd-derived (0..255 / 0xFFxx). No uid0 route demonstrated (gated on attacker-supplied AR2317 hardware); in-kernel ceiling is adjacent-heap corruption with semi-controlled data.
Evidence (decisive lines)
run.log mode 4: 'REPRODUCED: SIGSEGV si_addr=0x800476000' (== guard == struct end, 'si_addr is 2008 bytes past struct start'), 'FillVpdTable loop bound (uint16_t)(84-90) = 65530 entries (131060 bytes) into a 64-wide row', 'MATCH: unbounded OOB WRITE ran off the end of the ar2317State heap object'; run.log mode 5: same guard fault from the valid 102-half-dB curve; fix_run.log: fixD-only mode 5 completes (numPdGainsUsed=3), fixB+fixD mode 4 completes (ret=0, sentinel minPower).
PoC changes
New modes 4/5 added to the DF-4680 harness: object placed at a page end with a PROT_NONE guard page so OOB WRITEs fault at exactly struct end; modes 1-3 unchanged from the twin harness.
Verified recommended fix
Reject pd-gain curves with Pmax_t2 < Pmin_t2 or span >= MAX_PWR_RANGE_IN_HALF_DB before calling ar2317FillVpdTable / the interpolate loop (fix.diff; mirror to ar2413/ar2316/ar5413/ar2425).
Verdict
REPRODUCED on the DragonFly guest (verbatim-extraction harness, modes 4 and 5) -- a heap OOB WRITE one class stronger than the ar2317 twins and NOT covered by them: neither ar2317FillVpdTable's loop nor the interpolate loop clamps the pd-gain power span to the vpdTable row width (MAX_PWR_RANGE_IN_HALF_DB=64, ah_eeprom_v3.h:291) or validates Pmax>=Pmin. (Mode 4, wrap variant) a DESCENDING EEPROM pwr_t4 curve yields Pmax_t2=84 < Pmin_t2=90 (ar2317.c:372-382 performs no monotonicity check), so while (ii <= (uint16_t)(Pmax - Pmin)) at ar2317.c:289 wraps to ii<=65530 and the loop writes retVpdList[pdGainIdx][ii] for ii=0..65530 -- up to 131060 bytes marching from vpdTable_L[0][0] through the whole 2008-byte ar2317State and off its end; the harness (object placed with its end at a page boundary and a guard page after) faulted on the STORE at exactly the struct end, proving the write escaped the heap object. (Mode 5, clamp variant) a perfectly VALID ascending curve spanning 102 half-dB (>64) needs no integer wrap: FillVpdTable writes vpdTable_L/R row 3 indices 64..102 (intra-object spill) and the interpolate loop at ar2317.c:393-398 writes VpdTable_I[3][0..101] -- vpdTable_I is the LAST member, so indices >=64 land past the object end; faulted at exactly &vpdTable_I[3][64] == struct end. Written values are EEPROM-Vpd-derived (0..255, or 0xFFxx from negative extrapolation), so in-kernel this is a semi-controlled kernel-heap overwrite of up to ~128 KB followed by adjacent-object corruption or an immediate panic. The family pass-2s (DF-4619..4662) filed only the kk/gain-count/flat-curve siblings; this span defect is present in all five ar5212 RF files (ar2317.c:289 == ar2413.c:309 == ar2316.c:312 == ar5413.c:353 == ar2425.c:311) but is filed here first. Fix validated: fixD (reject inverted/oversized spans via 'continue') makes mode 5 complete with fixD alone; mode 4 completes with fixD+fixB (skipping the inverted gain leaves zero gains -- DF-4681's early return covers that residual).
No comments yet.