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

numPdGainsUsed==0 (EEPROM xpd_mask==0): uint32-wrapped wild reads at array+0x1FFFFFFFE (+8.59 GB) in the backfill loops + uninitialized Pmin_t2[0] stack read (twin of DF-4620/4649/4661)

Summary

xpd_mask==0 leaves zero gains; the compiled-out HALASSERT lets execution continue into uninit Pmin read and [ii-1]/[kk-1] wraps (uint32 counters at 0) - wild reads at +8.59 GB propagating into TPCRG5 and 32 PHY writes. Harness: wild pages seeded -> propagation observed; minPower run-to-run variance proves uninit. Early-return + caller-check fix (validated).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4681 Β· 13 files
FileTypeDescriptionSize
README.md β€” 1.9 KB ↓ raw
VERDICT.md β€” 2.8 KB ↓ raw
ar2317_harness.c β€” 16.5 KB view raw
build.sh β€” 85 B view raw
run.sh β€” 99 B view raw
fix_validate.sh β€” 2.8 KB view raw
fix.diff β€” 699 B view raw
build.log β€” 686 B view raw
run.log β€” 3.0 KB view raw
fix_run.log β€” 8.6 KB view raw
env.txt β€” 844 B view raw
manifest.json β€” 881 B view raw
verdict.json β€” 4.1 KB view raw

DF-4681 β€” ar2317: numPdGainsUsed==0 β†’ uint32-wrapped wild reads + uninit stack

Twin of DF-4620 (ar5413) / DF-4649 (ar2316) / DF-4661 (ar2413), verified for sys/dev/netif/ath/ath_hal/ar5212/ar2317.c.

Build

Same harness as DF-4680: cd /root/df2317 && sh build.sh

Run

./ar2317_harness 2
sh run.sh          # includes 3 extra runs showing minPower uninit variance

Expected (baseline)

mapped wild pages ... seeded 0x5A5A
gainBoundaries[0] = 0x5a5a
pcdacTable[0] = 0x5a5a
minPower out = 0xd4a0   (varies run-to-run)
DF-4681 REPRODUCED: uint32-underflow wild reads at ar2317.c:465/:469 + uninit
stack read at :405

With an EEPROM whose xpd_mask is 0 (all numVpd == 0, the exact dataset shape the shared SetupRawDataset produces, ah_eeprom_v3.c:656-673), numPdGainsUsed stays 0; HALASSERT(1 <= numPdGainsUsed ...) at :516 is compiled out in release (ah_internal.h:665-676) and the caller (:513-515) ignores the return, so:

  • :465 pPdGainBoundaries[ii-1] with ii==0 (uint32) β†’ element 0xFFFFFFFF β†’ read at gb + 0x1FFFFFFFE (+8.59 GB)
  • :469 pPDADCValues[kk-1] with kk==0 β†’ same wrap from pcdacTable
  • :405 *pMinCalPower = Pmin_t2[0] reads uninitialized stack

The harness maps the exact wild pages (kernel-direct-map emulation), seeds 0x5A5A, and shows both values propagate into outputs that reach hardware: gainBoundaries[0] β†’ AR_PHY_TPCRG5 (ar2317SetPowerTable:575-580), pcdacTable[0] β†’ 32 PHY register writes (:566-573).

Fix validation

fix.diff = early return 0 when numPdGainsUsed==0 (before the Pmin_t2[0] read) + numPdGainsUsed < 1 β†’ AH_FALSE in ar2317SetPowerTable. Patched mode 2 (fix_run.log): ret=0, minPower=0x7c7c (sentinel), gainBoundaries[0]=0x0000, pcdacTable[0]=0xaaaa (canary intact) β€” no wild reads, no uninit read.

Not re-reported: DF-1806 (numChannels==0, pass 1) β€” different trigger/guard.

VERDICT.md
↓ download raw

DF-4681 β€” VERDICT

Status: REPRODUCED (guest, verbatim-extraction harness, mode 2) Β· impact: dos Β· confidence: certain Β· fix: fixed (validated)

Root cause

ar2317getGainBoundariesAndPdadcsForPowers() only increments numPdGainsUsed when a gain has numVpd > 0 (ar2317.c:366-403). An EEPROM with xpd_mask == 0 yields all-zero numVpd (shared SetupRawDataset, ah_eeprom_v3.c:656-673), leaving numPdGainsUsed == 0, yet the function continues:

  • :405 *pMinCalPower = Pmin_t2[0]; β€” Pmin_t2[] was never written: uninitialized stack read, value flows into ah_txPowerIndexOffset (ar2317SetPowerTable:559-562). Observed run-to-run variance in the harness: 0xd4a0 / 0xd570 / 0xd3c0 / 0xd470.
  • :464-467 while (ii < MAX_NUM_PDGAINS_PER_CHANNEL) pPdGainBoundaries[ii] = pPdGainBoundaries[ii-1]; with ii==0: ii is uint32_t, so ii-1 = 0xFFFFFFFF β†’ read at gb + 0x1FFFFFFFE bytes (+8.59 GB) β€” a wild read, not a 2-byte under-read.
  • :468-471 while (kk < 128) pPDADCValues[kk] = pPDADCValues[kk-1]; with kk==0 β€” identical wrap from the pcdacTable base.

:516's HALASSERT(1 <= numPdGainsUsed && numPdGainsUsed <= 3) is a no-op in release kernels (AH_ASSERT undefined; ah_internal.h:665-676 β€” the guest GENERIC is such a build), and the caller (ar2317.c:513-515) never checks the return value.

Verification (run.log mode 2)

Direct-map survival emulation: the harness mmaps the two exact wild pages (gb+0x1FFFFFFFE, pcdacTable+0x1FFFFFFFE), seeds 0x5A5A, and the call completes, with both wild values propagating into hardware-facing outputs (gainBoundaries[0] β†’ AR_PHY_TPCRG5 at :575-580; pcdacTable[0] β†’ 32 PHY register writes at :566-573). The complementary fault case (same wrap arithmetic, unmapped target) is proven by mode 3/DF-4682's SIGSEGV at the predicted wild address. In-kernel: direct-map garbage (radio misprogrammed from wild-read values) or KVA-hole panic.

Fix validation (fix_run.log, fixB section)

fix.diff: if (numPdGainsUsed == 0) return 0; before the Pmin_t2[0] read, plus a numPdGainsUsed < 1 β†’ AH_FALSE guard in ar2317SetPowerTable. Patched: ret=0, sentinel minPower, gb[0]=0x0000, pcdac[0]=0xaaaa β€” clean. git apply --check clean; never applied to sys/.

Distinction from known findings

DF-1806 (pass 1) is the numChannels==0 β†’ idxL/idxR wild-read bug: different trigger (missing piers vs zero gains), different guard. DF-4682 is the same uint32-underflow class at :445-446 via flat curves β€” filed separately.

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: fix.diff (numPdGainsUsed==0 early return 0 before the Pmin_t2[0] read, plus a numPdGainsUsed<1 -> AH_FALSE check in ar2317SetPowerTable) applied to a copy of the pristine guest ar2317.c, function bodies re-extracted verbatim, harness rebuilt and re-run. Baseline propagates the 0x5A5A wild-read values; patched build returns 0, leaves minPower at the 0x7c7c sentinel, gainBoundaries[0]=0x0000 and pcdacTable[0]=0xaaaa (canary untouched): no wild reads, no uninit read. fix.diff is git-apply-able against the pristine tree and was never applied to sys/.

fix_run.log ('===== fixB' section: PATCHES_APPLIED, FIXED_BUILD_OK, ret=0/sentinel/no propagation) vs run.log mode 2; fix.diff in pack.
↓ fix.diffper-fix-DF-4681

Confirmed kernel references

Detail

Exploit chain

crafted v3 EEPROM (xpd_mask=0) on AR2317 card -> numPdGainsUsed==0 -> uint32-wrapped wild reads at gb+0x1FFFFFFFE and pcdacTable+0x1FFFFFFFE (direct-map garbage or KVA-hole panic) + uninit Pmin_t2[0] stack read -> garbage TPCRG5/pdadc programming or kernel panic. No uid0 route.

Evidence (decisive lines)

run.log mode 2: 'gainBoundaries[0] = 0x5a5a', 'pcdacTable[0] = 0x5a5a', 'DF-4681 REPRODUCED'; variance section: minPower 0xd570/0xd3c0/0xd470 (uninit variance); fix_run.log patched mode 2: ret=0, sentinel minPower 0x7c7c, gb[0]=0x0000, pcdac[0]=0xaaaa (canary intact, no propagation).

PoC changes

Same harness as DF-4680 (mode 2); no kernel-code changes.

Verified recommended fix

Early-return 0 when numPdGainsUsed==0 before the Pmin_t2[0] read, plus a caller-side numPdGainsUsed==0 -> AH_FALSE check in ar2317SetPowerTable (fix.diff).

Verdict

REPRODUCED on the DragonFly guest (verbatim-extraction harness, mode 2): with an EEPROM-shaped zero-gain dataset (xpd_mask=0 -> all numVpd=0, the exact shape the shared SetupRawDataset produces at ah_eeprom_v3.c:656-673) the function's backfill loops index with uint32 counters at 0: pPdGainBoundaries[ii-1] (ar2317.c:465) and pPDADCValues[kk-1] (ar2317.c:469) wrap to element index 0xFFFFFFFF and read at array+0x1FFFFFFFE bytes (+8.59 GB) -- the direct-map survival case proven by mapping the exact wild pages and observing both 0x5A5A values propagate into gainBoundaries[0] (-> AR_PHY_TPCRG5 by ar2317SetPowerTable:575-580) and pcdacTable[0] (-> 32 PHY reg writes, ar2317.c:566-573); the identical wrap arithmetic proven to fault in mode 3 (DF-4682). Plus uninitialized-stack read of Pmin_t2[0] at ar2317.c:405 (minPower varies run-to-run: 0xd4a0/0xd570/0xd3c0/0xd470). In-kernel this is a wild KVA read -> panic (KVA hole) or garbage radio programming; HALASSERT(:516) is compiled out in release and the caller (:513-515) never checks the return. Twin of DF-4620 (ar5413) / DF-4649 (ar2316) / DF-4661 (ar2413). Fix validated (early return + caller check): no wild reads, no uninit read.