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

ar2316: unbounded kk write in getGainBoundariesAndPdadcsForPowers overflows pcdacTable[128] into RF bank scratch (twin of DF-4619/ar5413)

Summary

The three pPDADCValues write loops run a uint32 kk with no bound while the sink is the 128-entry pcdacTable member. EEPROM-shaped curves (xpd_mask=0xF, Pmin {0,58,58,58}, register-realistic overlap 15) walk kk to 168: 80 bytes past the array into the RF bank scratch of the malloc'd struct (intra-object heap overflow, offsets verified), later programmed to the radio; the tail backfill is skipped leaving a stale table. VERBATIM-EXTRACTION harness on guest REPRODUCES (kk ran to 168, canary clobbered 0..79 bytes past; fix validated -> no overflow). Bound-all-three-loops fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4648 Β· 12 files
FileTypeDescriptionSize
README.md β€” 1.5 KB ↓ raw
VERDICT.md β€” 4.6 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 β€” 1.2 KB view raw
fix_validate.sh β€” 1.3 KB view raw
fix_run.log β€” 3.3 KB view raw

DF-4648 β€” ar2316 unbounded kk write (twin of DF-4619/ar5413)

PoC: verbatim-extraction harness proving the heap OOB write past pcdacTable[128] inside the ath_hal_malloced struct ar2316State.

Build (on the DragonFly guest, as root)

cd /root/df2316 && sh build.sh
# awk-extracts ar2316.c:251-499 verbatim from the pristine /usr/src tree
# into ar2316_extracted.inc (249 lines) and compiles:
#   cc -O2 -Wall -Wno-pointer-sign -o ar2316_harness ar2316_harness.c

Run

./ar2316_harness 1        # DF-4648 (this finding)
./ar2316_harness 2        # DF-4649 (numPdGainsUsed==0 wild reads)
./ar2316_harness 3        # DF-4650 (flat-curve [size-1]/[-2] wrap)
# or: sh run.sh           # all three

Expected output (mode 1)

sizeof(struct ar2316State)=2008 offsetof(pcdacTable)=72 Bank1Data=328 vpdTable_I=1492
mode=1 numPdGainsUsed(ret)=4 minPower=0x0000
DF-4648 REPRODUCED: pPDADCValues write walked past pcdacTable[127]: first
 clobbered u16 cell 0 bytes past array end, last 79 bytes past
 (value 0x007f) => kk ran to 168
  => intra-object heap OOB WRITE past pcdacTable[PWR_TABLE_SIZE_2316=128] into Bank data

Exit code 0 = reproduced; with fix.diff applied (see fix_validate.sh) mode 1 prints DF-4648: NO overflow detected and exits 1.

Threat model

Crafted EEPROM on an AR2316 (Atheros SoC 802.11) card: xpd_mask=0xF, Pmin pattern {0,58,58,58}, TPCRG5 overlap=15 (4-bit field). First channel set after attach walks kk to 168 and writes 80 bytes past the 128-entry table into the RF bank scratch. Hardware-gated; DoS-class.

VERDICT.md
↓ download raw

DF-4648 β€” ar2316: unbounded kk write in ar2316getGainBoundariesAndPdadcsForPowers β†’ heap OOB write past pcdacTable[128]

Verdict: REPRODUCED (harness, verbatim kernel code) β€” impact: dos (hardware-gated), twin of DF-4619 (ar5413)

Root cause

sys/dev/netif/ath/ath_hal/ar5212/ar2316.c β€” ar2316getGainBoundariesAndPdadcsForPowers() writes the final PDADC table through a uint32_t kk index into pPDADCValues in three unbounded loops:

  • ar2316.c:454-458 β€” extrapolate-below loop (pPDADCValues[kk++], iterations = -ss)
  • ar2316.c:464-465 β€” mid loop (pPDADCValues[kk++] = VpdTable_I[ii][ss++], iterations = maxIndex)
  • ar2316.c:474-482 β€” extrapolate-above loop (pPDADCValues[kk++], iterations = tgtIndex - maxIndex)

pPDADCValues is ahp->ah_pcdacTable (ar2316.c:512) = priv->pcdacTable, a uint16_t[PWR_TABLE_SIZE_2316=128] member (ar2316.c:41, PWR_TABLE_SIZE_2413=128 at ah_eeprom_v3.h:282) of the ath_hal_malloced struct ar2316State (ar2316.c:733). No loop checks kk < 128; only the tail backfill (ar2316.c:490-493) assumes kk <= 128.

Per-gain write count = max(0,-ss) + tgtIndex where ss = pPdGainBoundaries[ii-1] - Pmin_t2[ii] - pdGainOverlap_t2 and tgtIndex = pPdGainBoundaries[ii] + pdGainOverlap_t2 - Pmin_t2[ii]. pPdGainBoundaries is clamped to ≀63 (ar2316.c:436-441) but pdGainOverlap_t2 comes from the 4-bit AR_PHY_TPCRG5_PD_GAIN_OVERLAP hardware field (ar2316.c:532-533; mask 0xF, ar5212phy.h:349-350) β†’ 0..15, and Pmin_t2 is EEPROM-derived. Summed over 4 gains the algebra allows kk up to 3 + 63 + 6*overlap = 156 > 128 with realistic register values (overlap 15), so up to 28 cells (56 bytes) past the array; the measured walk with the crafted dataset below runs kk to 168 (40 cells / 80 bytes past) into Bank1Data..Bank7Data (offsets verified: pcdacTable@72, Bank1Data@328, struct size 2008) β€” an intra-object heap overflow of the ath_hal_malloc allocation. The corrupted bank scratch is programmed to the radio at the next ar2316SetRfRegs (ar2316.c:212-216).

Trigger (threat model)

Crafted EEPROM on an AR2316-bearing card (physical / supply-chain attacker): xpd_mask=0xF (4 used pdGains), pwr_t4 curves giving the {0,58,58,58} Pmin pattern (pwr_t4[0]=4pwr_I, +2delta per point β€” the exact shape ar2413SetupRawDataset/ar2413EepromToRawDataset produce, ah_eeprom_v3.c:633-728), TPCRG5 overlap set to 15. First channel set after attach invokes ar2316SetPowerTable (ar2316.c:501) β†’ the kk walk (ar2316.c:535-537 call, no return-value check beyond the compiled-out HALASSERT at :538).

How it was verified

Verbatim-extraction harness (ar2316_harness.c mode 1): the guest's pristine /usr/src ar2316.c function bodies (lines 251-499) are sed/awk-extracted at build time (see build.sh, anchor-based) and compiled against a faithful struct ar2316State (member order from ar2316.c:39-61, bank rows 1/1/1/29/3 counted from the shipped ar5212.ini). HALASSERT compiled out to mirror the release build (ah_internal.h:665-676). The bank region is pre-filled with 0xAAAA canaries; mode 1 reports the first/last clobbered cell.

run.log (decisive lines):

mode=1 numPdGainsUsed(ret)=4 minPower=0x0000
DF-4648 REPRODUCED: pPDADCValues write walked past pcdacTable[127]: first
 clobbered u16 cell 0 bytes past array end, last 79 bytes past
 (value 0x007f) => kk ran to 168
  => intra-object heap OOB WRITE past pcdacTable[PWR_TABLE_SIZE_2316=128]

Reproduced twice (run.log, run.2.log) with identical results.

Exploitation ceiling

Hardware-gated (needs attacker-supplied AR2316 card with crafted EEPROM). The overflow stays inside the 2008-byte struct ar2316State allocation (banks + vpd tables are the landing zone), values are constrained (pdadc clamps 0/127, uint16 Vpd-derived). Realistic ceiling: corrupted RF bank programming / radio malfunction / garbage tx power β€” a DoS-class defect, not a uid0 route. No exfiltration channel.

Fix validation

fix.diff bounds all three write loops with kk < PWR_TABLE_SIZE_2316 (mirrors the accepted DF-4619 fix). fix_run.log (PATCHED mode 1): DF-4648: NO overflow detected, bank canaries intact. Modes 2/3 still reproduce on this diff (it only touches the kk loops) β€” those are DF-4649/DF-4650, fixed by their own diffs; the combined patch leaves all three modes clean.

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 bounds the three pPDADCValues write loops with kk < PWR_TABLE_SIZE_2316; on the patched extraction mode 1 reports 'DF-4648: NO overflow detected' with the bank canary region intact (baseline reproduced 168-cell walk, 80 bytes past). Harness-level validation of the patched function bodies (re-extracted from a fix.diff-patched copy); kernel-build validation is hardware-gated (no AR2316 on the guest).

fix_run.log (PATCHED mode 1): 'DF-4648: NO overflow detected'
↓ fix.diffper-fix-DF-4648

Confirmed kernel references

Detail

Exploit chain

crafted EEPROM (xpd_mask=0xF, Pmin {0,58,58,58}, overlap 15) -> ath attach reads cal dataset -> first channel set invokes ar2316SetPowerTable:535 -> getGainBoundariesAndPdadcsForPowers kk walk exceeds 128 -> pPDADCValues[128..167] overwrite Bank1/2/3/6/7Data -> corrupted RF banks programmed to the radio at next ar2316SetRfRegs (ar2316.c:212-216) + stale pdadc tail. No uid0 route: hardware-gated, intra-object, clamped 16-bit values.

Evidence (decisive lines)

run.log mode 1: 'DF-4648 REPRODUCED: ... first clobbered u16 cell 0 bytes past array end, last 79 bytes past ... kk ran to 168'; run.2.log identical; fix_run.log PATCHED mode 1: 'DF-4648: NO overflow detected' with bank canary intact; build.sh shows the verbatim extraction from /usr/src.

PoC changes

harness written from scratch (no seed PoC), modeled on the DF-4619 ar5413 pack: anchor-based verbatim extraction of ar2316.c:251-499 at build time on the guest, faithful struct (bank rows 1/1/1/29/3 counted from the shipped ar5212.ini), HALASSERT compiled out to mirror release (AH_ASSERT undefined), dataset crafted per ar2413SetupRawDataset/ar2413EepromToRawDataset shapes; spans >=2 chosen to isolate DF-4648 from the independent DF-4650 wrap; overlap 15 justified from the 4-bit TPCRG5 mask (unlike a 2-bit assumption).

Verified recommended fix

Bound all three pPDADCValues write loops with kk < PWR_TABLE_SIZE_2316 (see fix.diff).

Verdict

REPRODUCED on the DragonFly guest by compiling the verbatim kernel function bodies (anchor-extracted ar2316.c:251-499 from /usr/src, 249 lines) against a faithful ar2316State layout (bank rows 1/1/1/29/3 from the shipped ar5212.ini; verified offsets pcdac@72 Bank1@328 vpdI@1492 sizeof 2008): with an EEPROM-shaped crafted dataset (xpd_mask=0xF, Pmin pattern {0,58,58,58}, TPCRG5 overlap 15 -- register-realistic, the field is 4 bits, ar5212phy.h:349) the unbounded kk walk at ar2316.c:454/:464/:474 wrote 40 uint16 cells (80 bytes) past pcdacTable[PWR_TABLE_SIZE_2316=128] into the Bank1Data..Bank7Data members of the heap-allocated ar2316State object (intra-object overflow; RF bank scratch corrupted, reprogrammed to the radio at ar2316.c:212-216). Exploitation is gated on attacker-supplied AR2316 hardware with crafted EEPROM (physical/supply-chain); realistic ceiling is radio misprogramming/DoS, not privilege escalation. Fix validated: with kk<PWR_TABLE_SIZE_2316 guards the overflow is gone (bank canary intact, fix_run.log).