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)
PoC verification
Evidence pack
findings/poc/DF-4660 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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).
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
fixedValidated 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.
Confirmed kernel references
- sys/dev/netif/ath/ath_hal/ar5212/ar2413.c:451
- sys/dev/netif/ath/ath_hal/ar5212/ar2413.c:453
- sys/dev/netif/ath/ath_hal/ar5212/ar2413.c:462
- sys/dev/netif/ath/ath_hal/ar5212/ar2413.c:476
- sys/dev/netif/ath/ath_hal/ar5212/ar2413.c:37
- sys/dev/netif/ath/ath_hal/ar5212/ar2413.c:510
- sys/dev/netif/ath/ath_hal/ar5212/ar2413.c:747
- sys/dev/netif/ath/ath_hal/ar5212/ar2413.c:206
- sys/dev/netif/ath/ath_hal/ah_eeprom_v3.h:282
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).
No comments yet.