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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4662 Β· 13 files
FileTypeDescriptionSize
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).

VERDICT.md
↓ download raw

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

fixed
baseline reproduced→ patch + rebuild →patched clean

Validated 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.
↓ fix.diffper-fix-DF-4662

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.