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

ar2316: flat EEPROM power curve -> VpdTable_I[ii][sizeCurrVpdTable-1/-2] uint32-wrap wild reads (twin of DF-4622/ar5413)

Summary

A used pdGain with a flat EEPROM curve (all pwr_delta zero) gives sizeCurrVpdTable 0/1, so the [-1]/[-2] subscripts wrap to 0xFFFFFFFF/0xFFFFFFFE - reads at row+0x1FFFFFFFE (+8.59 GB) whose garbage Vpd_step feeds extrapolated PDADC values programmed to the PHY. Harness SIGSEGV si_addr EXACTLY equals row+0x1FFFFFFFE (asserted by handler); clamp>=2 fix validated. Not necessarily malicious - a flat-calibrated legit card hits it.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4650 Β· 12 files
FileTypeDescriptionSize
README.md β€” 999 B ↓ raw
VERDICT.md β€” 3.0 KB ↓ raw
ar2316_harness.c β€” 12.4 KB view raw
build.sh β€” 191 B view raw
run.sh β€” 99 B view raw
build.log β€” 632 B view raw
run.log β€” 1.4 KB view raw
run.2.log β€” 1.4 KB view raw
env.txt β€” 346 B view raw
fix.diff β€” 777 B view raw
fix_validate.sh β€” 1.3 KB view raw
fix_run.log β€” 3.3 KB view raw

DF-4650 β€” ar2316 flat-curve [size-1]/[-2] uint32 wrap (twin of DF-4622/ar5413)

PoC: verbatim-extraction harness, mode 3. Same build as DF-4648 (see that README; self-contained copies here).

Build (guest, root)

sh build.sh    # verbatim-extract ar2316.c:251-499 + cc -O2 -Wno-pointer-sign

Run

./ar2316_harness 3

Expected output

sizeof(struct ar2316State)=2008 offsetof(pcdacTable)=72 Bank1Data=328 vpdTable_I=1492
REPRODUCED: SIGSEGV si_addr=0x200402872, expected wild address 0x200402872
MATCH: wild OOB read proven (ar2316.c uint32 counter underflow)

Exit 0 = reproduced (si_addr exactly equals &vpdTable_I[0][0] + 0x1FFFFFFFE). With fix.diff (sizeCurrVpdTable clamped β‰₯2) mode 3 completes without faulting and prints sane values.

Threat model

Crafted EEPROM on an AR2316 card: a used pdGain with all-zero pwr_delta_t2 (flat curve) makes sizeCurrVpdTable==0 β†’ VpdTable_I[ii][-1]/[-2] wrap to a +8.59 GB wild read at ar2316.c:467-468.

VERDICT.md
↓ download raw

DF-4650 β€” ar2316: flat EEPROM power curve β†’ VpdTable_I[ii][sizeCurrVpdTable-1/-2] uint32-wrap wild reads (twin of DF-4622/ar5413)

Verdict: REPRODUCED (harness, verbatim kernel code, exact-address SIGSEGV) β€” impact: dos (hardware-gated)

Root cause

sys/dev/netif/ath/ath_hal/ar5212/ar2316.c β€” ar2316getGainBoundariesAndPdadcsForPowers():

460:  sizeCurrVpdTable = Pmax_t2[ii] - Pmin_t2[ii];   /* uint32_t */
461:  tgtIndex = pPdGainBoundaries[ii] + pdGainOverlap_t2 - Pmin_t2[ii];
...
467:  Vpd_step = (uint16_t)(VpdTable_I[ii][sizeCurrVpdTable-1] -
468:                         VpdTable_I[ii][sizeCurrVpdTable-2]);

Pmax_t2/Pmin_t2 are halves of the EEPROM pwr_t4 curve endpoints (ar2316.c:394-404). If the curve for a used pdGain is flat (all EEPROM pwr_delta_t2 deltas zero β†’ pwr_t4[numVpd-1] == pwr_t4[0]), then sizeCurrVpdTable == 0 and sizeCurrVpdTable-1 / -2 wrap the uint32_t to 0xFFFFFFFF / 0xFFFFFFFE β†’ reads at VpdTable_I[ii] + 0x1FFFFFFFE bytes (+8.59 GB wild read). With sizeCurrVpdTable == 1 (single step) the -2 term wraps. Nothing guards the subtraction; the neighboring unconditional read VpdTable_I[ii][1] - VpdTable_I[ii][0] (ar2316.c:449) stays in-row and is not itself OOB.

In the kernel the wild read lands in the direct map (garbage Vpd_step β†’ garbage extrapolated PDADC values programmed to the radio) or in a KVA hole β†’ panic.

Trigger (threat model)

Crafted EEPROM on an AR2316-bearing card: any used pdGain whose pwr_delta_t2[kk] entries are all zero for the channel pair bracketing the target channel (ar2413EepromToRawDataset then yields pwr_t4 = {v,v,v,v}, ah_eeprom_v3.c:715-719).

How it was verified

Verbatim-extraction harness (ar2316_harness.c mode 3): four used gains with flat curves (pwr_t4[0]=124, step 0). The wild address is left unmapped so the wrap faults; the SIGSEGV handler asserts si_addr == &vpdTable_I[0][0] + 0x1FFFFFFFE exactly.

run.log (decisive lines):

REPRODUCED: SIGSEGV si_addr=0x200402872, expected wild address 0x200402872
MATCH: wild OOB read proven (ar2316.c uint32 counter underflow)

The exact-address match proves the read is the [sizeCurrVpdTable-1] uint32 wrap (element index 0xFFFFFFFF Γ— 2 bytes), not any other access.

Exploitation ceiling

Hardware-gated (crafted EEPROM). Wild KVA read β†’ panic or garbage radio programming. No exfiltration channel, no uid0 route.

Fix validation

fix.diff (mirrors the accepted DF-4622 fix): clamp sizeCurrVpdTable to β‰₯2 before the reads. fix_run.log (PATCHED mode 3): completes normally (no SIGSEGV), minPower=0x003e β€” a sane value derived from the dataset.

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

fix.diff clamps sizeCurrVpdTable to >=2 (in-row reads); on the patched extraction mode 3 completes with no SIGSEGV and sane outputs (minPower=0x003e). Harness-level validation of the patched function bodies; kernel-build validation hardware-gated (no AR2316 on guest).

fix_run.log '--- PATCHED mode 3' section: completes, 'mode=3 numPdGainsUsed(ret)=4 minPower=0x003e', no fault
↓ fix.diffper-fix-DF-4650

Confirmed kernel references

Detail

Exploit chain

crafted EEPROM flat curve (all pwr_delta_t2=0 for a used pdGain) -> sizeCurrVpdTable==0 at ar2316.c:460 -> :467-468 uint32 index wrap -> wild KVA read at vpdTable row +0x1FFFFFFFE -> panic (KVA hole) or garbage Vpd_step -> garbage PDADC extrapolation programmed to the PHY.

Evidence (decisive lines)

run.log mode 3: 'REPRODUCED: SIGSEGV si_addr=0x200402872, expected wild address 0x200402872' + 'MATCH: wild OOB read proven (ar2316.c uint32 counter underflow)'; run.2.log identical; fix_run.log PATCHED mode 3: completes, minPower=0x003e (sane), no fault.

PoC changes

no seed PoC; mode 3 of the shared verbatim-extraction harness (same build.sh as DF-4648): flat-curve dataset crafted so sizeCurrVpdTable==0 for every gain; wild address left unmapped so the wrap faults, with the SIGSEGV handler asserting the exact expected wild address.

Verified recommended fix

Clamp sizeCurrVpdTable to >=2 before the [size-1]/[size-2] reads at ar2316.c:467-468 (see 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 -> sizeCurrVpdTable==0) the reads VpdTable_I[ii][sizeCurrVpdTable-1/-2] at ar2316.c:467-468 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 (0x200402872), asserted in the handler. In-kernel the same wild read lands in the direct map (garbage Vpd_step -> garbage extrapolated PDADC values programmed to the radio via ar2316.c:474-482/:588-595) or in a KVA hole -> panic. sizeCurrVpdTable==1 (single-step curve) wraps the -2 term identically. Hardware-gated (crafted EEPROM on an AR2316 card); no exfil channel, no uid0 route. Fix validated: clamping sizeCurrVpdTable to >=2 makes mode 3 complete without faulting.