β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4682

Flat pd-gain curve makes sizeCurrVpdTable==0 β†’ VpdTable_I[ii][sizeCurrVpdTable-1/-2] uint32-wrap wild reads at row+0x1FFFFFFFE (twin of DF-4622/4650/4662)

Summary

Equal pwr_t4 points (a legal flat calibration, no malformed data) give span 0, so the [-1]/[-2] subscripts wrap - reads at row+0x1FFFFFFFE (harness SIGSEGV si_addr exact-match asserted). Guard>=2 fix (validated).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4682 Β· 13 files
FileTypeDescriptionSize
README.md β€” 1.4 KB ↓ raw
VERDICT.md β€” 2.2 KB ↓ raw
ar2317_harness.c β€” 16.5 KB view raw
build.sh β€” 85 B view raw
run.sh β€” 99 B view raw
fix_validate.sh β€” 2.8 KB view raw
fix.diff β€” 1.1 KB 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 β€” 859 B view raw
verdict.json β€” 3.8 KB view raw

DF-4682 β€” ar2317: flat pd-gain curve β†’ sizeCurrVpdTable-1/-2 uint32 wrap

Twin of DF-4622 (ar5413) / DF-4650 (ar2316) / DF-4662 (ar2413), verified for sys/dev/netif/ath/ath_hal/ar5212/ar2317.c.

Build

Same harness as DF-4680: cd /root/df2317 && sh build.sh

Run

./ar2317_harness 3

Expected (baseline)

REPRODUCED: SIGSEGV si_addr=0xa00475dfe
  expected wild READ address 0xa00475dfe (uint32 index wrap ...)
MATCH: wild OOB read proven (ar2317.c uint32 counter underflow)

A flat EEPROM power curve (all pwr_t4 deltas zero β†’ Pmax_t2 == Pmin_t2) makes sizeCurrVpdTable = Pmax_t2[ii] - Pmin_t2[ii] (ar2317.c:438, uint32 arithmetic) zero; the Vpd_step computation at :445-446 then reads VpdTable_I[ii][sizeCurrVpdTable-1] and [sizeCurrVpdTable-2] β€” element indices 0xFFFFFFFF / 0xFFFFFFFE β†’ wild reads at row + 0x1FFFFFFFE (+8.59 GB). Left unmapped in userspace, the harness faults at exactly the predicted address. No malformed EEPROM is even required β€” degenerate flat cal curves suffice. (:427 also reads VpdTable_I[ii][1], left uninitialized in this shape β€” side observation.)

Fix validation

fix.diff guards the [-1]/[-2] reads with sizeCurrVpdTable >= 2 (Vpd_step=1 fallback) and clamps the extrapolate-above last index. Patched mode 3 (fix_run.log): completes with sane minPower=0x003e, no SIGSEGV.

Not re-reported: DF-1806 (numChannels==0), DF-4681 (zero-gain) β€” different triggers, different guards.

VERDICT.md
↓ download raw

DF-4682 β€” VERDICT

Status: REPRODUCED (guest, verbatim-extraction harness, mode 3) Β· impact: dos Β· confidence: certain Β· fix: fixed (validated)

Root cause

sizeCurrVpdTable = Pmax_t2[ii] - Pmin_t2[ii]; (ar2317.c:438) is uint32_t arithmetic over int16_t values. An EEPROM curve whose pwr_t4 points are all equal (flat calibration β€” two identical pier powers, delta fields 0) yields Pmax_t2 == Pmin_t2, so sizeCurrVpdTable == 0, and:

Vpd_step = (uint16_t)(VpdTable_I[ii][sizeCurrVpdTable-1] -   /* [0xFFFFFFFF] */
               VpdTable_I[ii][sizeCurrVpdTable-2]);    /* [0xFFFFFFFE] */

(ar2317.c:445-446) β€” wild reads at row + 0x1FFFFFFFE bytes. The same wrap pattern repeats in the extrapolate-above block (:455). Additionally :427 reads VpdTable_I[ii][1], which ar2317FillVpdTable left uninitialized when the span is 0 (its while (ii <= 0) writes only index 0).

Verification (run.log mode 3)

Four gains, numVpd=4, pwr_t4 all 124 β†’ Pmin=Pmax=62, sizeCurrVpdTable=0. The harness leaves the wild target unmapped: SIGSEGV with si_addr == &vpdTable_I[0][0] + 0x1FFFFFFFE exactly (asserted in the handler; run.log shows the MATCH line). In-kernel the same address hits the direct map (garbage Vpd_step β†’ extrapolated pdadc values β†’ pcdacTable β†’ 32 PHY writes, :566-573) or a KVA hole (panic).

Flat curves are the mildest trigger of the three ar2317 siblings β€” no malformed EEPROM needed, just equal calibrated power points.

Fix validation (fix_run.log, fixC section)

fix.diff: if (sizeCurrVpdTable >= 2) { ...existing Vpd_step math... } else Vpd_step = 1; plus vpdLastIdx = (sizeCurrVpdTable >= 1) ? sizeCurrVpdTable-1 : 0 for the extrapolate-above reference. Patched mode 3 completes with minPower=0x003e (= 62, the flat curve's Pmin β€” sane) and no fault. git apply --check clean; 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

fixed
baseline reproduced→ patch + rebuild →patched clean

Validated on the guest: fix.diff (sizeCurrVpdTable >= 2 guard around the [-1]/[-2] Vpd_step reads with a Vpd_step=1 fallback, and a clamped vpdLastIdx for the extrapolate-above reference) applied to a copy of the pristine guest ar2317.c, function bodies re-extracted verbatim, harness rebuilt and re-run. Baseline SIGSEGVs at the wild address; patched mode 3 completes without fault (minPower=0x003e sane). fix.diff is git-apply-able against the pristine tree and was never applied to sys/.

fix_run.log ('===== fixC' section: PATCHES_APPLIED, FIXED_BUILD_OK, 'no SIGSEGV: function completed') vs run.log mode 3; fix.diff in pack.
↓ fix.diffper-fix-DF-4682

Confirmed kernel references

Detail

Exploit chain

crafted (or merely degenerate-flat) v3 EEPROM power curves on AR2317 card -> sizeCurrVpdTable==0 -> VpdTable_I[ii][0xFFFFFFFF/0xFFFFFFFE] wild reads at row+0x1FFFFFFFE -> direct-map garbage propagates into Vpd_step -> extrapolated pdadc values -> pcdacTable + 32 PHY register writes (ar2317.c:566-573) = radio misprogramming, or KVA-hole kernel panic. No uid0 route.

Evidence (decisive lines)

run.log mode 3: 'REPRODUCED: SIGSEGV si_addr=0xa00475dfe' == 'expected wild READ address 0xa00475dfe' + 'MATCH: wild OOB read proven'; fix_run.log ('===== fixC' section): patched mode 3 completes with minPower=0x003e, no SIGSEGV.

PoC changes

Same harness as DF-4680 (mode 3); no kernel-code changes.

Verified recommended fix

Guard the sizeCurrVpdTable-1/-2 reads with a sizeCurrVpdTable >= 2 check (Vpd_step=1 fallback) and clamp the extrapolate-above last index (fix.diff).

Verdict

REPRODUCED on the DragonFly guest (verbatim-extraction harness, mode 3): with 4 pdGains whose EEPROM power curves are FLAT (all pwr_t4 deltas zero -> Pmax_t2 == Pmin_t2), sizeCurrVpdTable = Pmax_t2[ii] - Pmin_t2[ii] (ar2317.c:438, uint32 arithmetic of int16 values) is 0, and the Vpd_step computation at ar2317.c:445-446 reads VpdTable_I[ii][sizeCurrVpdTable-1] and [sizeCurrVpdTable-2] -- element indices 0xFFFFFFFF and 0xFFFFFFFE under uint32 arithmetic -- i.e. wild reads at row+0x1FFFFFFFE bytes (+8.59 GB). Left unmapped, the harness faulted with SIGSEGV si_addr == &vpdTable_I[0][0] + 0x1FFFFFFFE exactly (asserted in the handler); in-kernel the same address is a direct-map garbage read (value flows into Vpd_step and the extrapolation above, then into pcdacTable and PHY registers) or a KVA-hole panic. Side observation: line 427 also reads VpdTable_I[ii][1] which FillVpdTable left uninitialized in this shape (intra-row uninit read). Flat curves require no EEPROM 'error' -- equal calibrated pwr points produce them -- making this the easiest trigger of the three ar2317 siblings. Twin of DF-4622 (ar5413) / DF-4650 (ar2316) / DF-4662 (ar2413). Fix validated (sizeCurrVpdTable>=2 guard + clamped last-index): completes with no fault.