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

ar2413: unbounded kk pdadc walk writes past pcdacTable[PWR_TABLE_SIZE_2413=128] β€” intra-object heap OOB write corrupting RF bank scratch (twin of DF-4619/ar5413, DF-4648/ar2316)

Summary

The three pPDADCValues[kk++] loops never check kk against the 128-entry table; EEPROM-crafted Pmin patterns with the register-realistic overlap walk kk to 168 - 80 bytes past the array into the RF bank scratch later reprogrammed to the radio (per-gain contributions sum above 128 for 4 gains by construction). VERBATIM-EXTRACTION harness on guest REPRODUCES (kk ran to 168, canary clobbered; fix validated -> no overflow). Bound-all-three-loops fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4660 Β· 13 files
FileTypeDescriptionSize
README.md β€” 2.1 KB ↓ raw
VERDICT.md β€” 3.4 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 β€” 1.1 KB 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 β€” 947 B view raw
verdict.json β€” 4.4 KB view raw

DF-4660 β€” ar2413: unbounded kk pdadc walk writes past pcdacTable[128]

Twin of DF-4619 (ar5413) / DF-4648 (ar2316), same defect in the AR2413 RF support. ar2413getGainBoundariesAndPdadcsForPowers() (ar2413.c:353-496) fills the 128-entry pdadc table (pPDADCValues == priv->pcdacTable[128], ar2413.c:37/510/747) with three unguarded pPDADCValues[kk++] loops (ar2413.c:453, :462, :476). kk is never checked against PWR_TABLE_SIZE_2413; with EEPROM-crafted Pmin/Pmax patterns and the register-realistic max 4-bit pdGainOverlap (15), the walk for 4 used gains runs to 168 β€” 40 uint16 cells (80 bytes) past the array, into the Bank1Data..Bank7Data members of the heap-allocated struct ar2413State (intra-object overflow), which are then programmed to the radio (ar2413.c:206-210).

Build

On the DragonFly guest (dfbsd-qemu):

cd /root/df2413 && sh build.sh

build.sh verbatim-extracts ar2413.c:245-496 (GetLowerUpperIndex, ar2413FillVpdTable, interpolate_signed, ar2413getGainBoundariesAndPdadcsForPowers) from the pristine guest /usr/src copy (md5-verified identical to the audit tree) and compiles them against a faithful struct ar2413State (bank rows 1,1,1,27,3 from the shipped ar5212.ini; verified offsets pcdac@72 Bank1@328 vpdI@1484, sizeof 2000).

Run

./ar2413_harness 1        # mode 1 = this finding

Expected

DF-4660 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

The bank region is 0xAAAAAAAA-prefilled; any cell != 0xAAAA (u16 view) was clobbered by the overflowing walk. (modes 2/3 of the same harness reproduce DF-4661/DF-4662.)

Fix validation

sh fix_validate.sh applies fixA/B/C (= this pack's fix.diff + the two sibling fixes) to a copy of the pristine source, rebuilds, re-runs: patched mode 1 reports DF-4660: NO overflow detected.

Threat model: attacker-crafted EEPROM on an AR2413 (5212-family b/g) card (physical / supply-chain). Realistic ceiling: radio misprogramming / kernel panic β€” no exfil, no uid0 route (values constrained to 0..127, intra-object).

VERDICT.md
↓ download raw

DF-4660 β€” VERDICT

REPRODUCED (impact: dos / memcorrupt β€” intra-object heap OOB write; hardware-gated trigger)

What was run

Verbatim-extraction harness on the DragonFly 6.5-DEVELOPMENT guest (dfbsd-qemu; uname in env.txt): the real kernel function bodies of sys/dev/netif/ath/ath_hal/ar5212/ar2413.c:245-496 were extracted at build time from the guest's pristine /usr/src (md5 0589bc4f179fb76901aa6fd0f9865e23 == audit tree) and compiled unmodified against a faithful struct ar2413State (member order from ar2413.c:35-57; bank row counts 1,1,1,27,3 counted from the shipped ar5212.ini; runtime-printed offsets: pcdacTable@72, Bank1Data@328, vpdTable_I@1484, sizeof 2000 β€” matches the kernel object's layout).

Result (run.log, reproducible across runs β€” run.log "repeat" section)

With an EEPROM-shaped crafted dataset (xpd_mask=0xF, Pmin pattern {0,58,58,58} as pwr_t4 = {0,4,8,12} / {116,...}, TPCRG5 overlap 15 β€” the 4-bit field maximum, ar5212phy.h), numPdGainsUsed=4 and the three unguarded pPDADCValues[kk++] loops (ar2413.c:453, :462, :476) drove kk to 168 while pPDADCValues is priv->pcdacTable[PWR_TABLE_SIZE_2413=128] (ar2413.c:37; wired at ar2413.c:510 and ar2413.c:747):

first clobbered u16 cell 0 bytes past array end, last 79 bytes past
(value 0x007f) => kk ran to 168

=> 40 uint16 cells (80 bytes) written past the array into Bank1Data[1]/Bank2Data[1]/Bank3Data[1]/Bank6Data[27]/Bank7Data[3] (detected via 0xAAAAAAAA canary). The corrupted banks are subsequently programmed to the RF hardware at ar2413.c:206-210 (HAL_INI_WRITE_BANK).

kk-bound arithmetic: each gain contributes up to pPdGainBoundaries[ii] (≀63, clamped) + pdGainOverlap_t2 (≀15) βˆ’ Pmin_t2[ii] entries; with 4 gains β‰₯ 3+63+6Β·15 = 156 > 128 minimum-guaranteed; measured 168.

Why this is the same bug as DF-4619/DF-4648

ar2413.c:451-479 is line-for-line the ar2316/ar5413 loop (only types of the Vpd tables' helpers differ); DragonFly carries all three RF backends.

Exploitation ceiling

Gated on attacker-supplied AR2413 hardware with crafted EEPROM (v3 EEPROM image, physical/supply-chain). In-kernel this is a heap intra-object overflow inside the 2000-byte ath_hal_malloc allocation: values constrained (pdadc clamped 0/127), overflow lands in RF-bank scratch that is immediately rewritten from the ini on the next reset path. Realistic ceiling: radio misprogramming / crash (DoS). No info leak, no privilege-escalation route.

Fix validation (fix_run.log)

fix.diff (git-apply-able, verified git apply --check against the pristine tree) adds kk < PWR_TABLE_SIZE_2413 guards to all three loops. Patched harness (same verbatim extraction from the patched copy): mode 1 reports DF-4660: NO overflow detected (bank canary intact). Baseline vs patched recorded in run.log / fix_run.log.

Known findings NOT re-reported here

DF-1796 (numChannels==0 OOB read in the same function, pass 1) is distinct. The sibling wraps found in the same function are filed separately as DF-4661 (numPdGainsUsed==0) and DF-4662 (flat-curve wrap).

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-4660

Confirmed kernel references

Detail

Exploit chain

crafted v3 EEPROM on AR2413 card -> ath attach reads ee_rawDataset2413 -> ar5212SetTransmitPower -> ar2413SetPowerTable -> ar2413getGainBoundariesAndPdadcsForPowers unbounded kk walk -> 80-byte intra-object heap OOB write past pcdacTable[128] into RF bank scratch -> banks programmed to radio (ar2413.c:206-210): radio misprogramming / crash. No uid0 route (values clamped 0..127, intra-object only).

Evidence (decisive lines)

run.log mode 1: 'DF-4660 REPRODUCED ... kk ran to 168'; build.log struct offsets (pcdac@72 Bank1@328 vpdI@1484 sizeof 2000); fix_run.log patched mode 1: 'NO overflow detected'; fix.diff (git-apply verified).

PoC changes

Harness ported from the DF-4648 (ar2316) twin: bank row counts 1/1/1/27/3 (ar5212.ini ar5212Bank*_2413), ar2413 names/anchors, 66-cell bank canary window; extraction anchored (not line-based). No kernel-code changes -- function bodies compiled verbatim.

Verified recommended fix

Add kk < PWR_TABLE_SIZE_2413 guards to all three pPDADCValues[kk++] loops in ar2413getGainBoundariesAndPdadcsForPowers (fix.diff).

Verdict

REPRODUCED on the DragonFly guest by compiling the verbatim kernel function bodies (anchor-extracted ar2413.c:245-496 from /usr/src, md5-verified == audit tree) against a faithful ar2413State layout (bank rows 1/1/1/27/3 from the shipped ar5212.ini; verified offsets pcdac@72 Bank1@328 vpdI@1484 sizeof 2000): 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) the unbounded kk walk at ar2413.c:453/:462/:476 wrote 40 uint16 cells (80 bytes) past pcdacTable[PWR_TABLE_SIZE_2413=128] into the Bank1Data..Bank7Data members of the heap-allocated ar2413State object (intra-object overflow; RF bank scratch corrupted, reprogrammed to the radio at ar2413.c:206-210). kk ran to 168; with 63-clamped boundaries and overlap 15 the minimum-guaranteed walk is 3+63+6*15=156 > 128. Exploitation is gated on attacker-supplied AR2413 hardware with crafted EEPROM (physical/supply-chain); realistic ceiling is radio misprogramming/DoS, not privilege escalation. Twin of DF-4619 (ar5413) / DF-4648 (ar2316). Fix validated: with kk<PWR_TABLE_SIZE_2413 guards the overflow is gone (bank canary intact, fix_run.log).