ar5212GetLowerUpperValues with an empty pier list: uninitialized stack read (lp[0]) and 2-byte OOB stack read (ep[-1]) below tempChannelList, garbage pier selection can program MAX_RATE_POWER
Summary
With listSize==0 (blanked EEPROM target-power group or an all-zero CTL edge block), GetLowerUpperValues reads the never-written tempChannelList[0] and ep[-1] one uint16 below the array; garbage lower/upper flow into walks whose HALASSERT guard is compiled out in release, letting an empty CTL's entry-0 power - or MAX_RATE_POWER (63) - be selected for the channel (regulatory TX-power bypass). Confined to two stack reads in a mapped frame - no disclosure, no write primitive. All other ar5416-checklist families driven to verified negatives (ctlEdges counts bounded per-EEPROM-rev, spur masks <=31 shifts, gain-delta range analysis).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3977 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.9 KB | β raw | |
| VERDICT.md | β | 2.6 KB | β raw | |
| verdict.json | β | 2.6 KB | view raw | |
| manifest.json | β | 893 B | view raw | |
| fix.diff | β | 910 B | view raw |
DF-3977 β ar5212GetLowerUpperValues with an empty pier list: uninitialized stack reads and a 2-byte OOB stack read (ep[-1]) below tempChannelList
- File:
sys/dev/netif/ath/ath_hal/ar5212/ar5212_reset.c:2626-2669(sink) callers:ar5212GetTargetPowers(2568-2583) andar5212GetMaxEdgePower(2492-2508) - Class: CWE-125 / CWE-457 (Low) β checklist items (a)/(c) pass-2 verification for this HAL.
Build / Run / Expected
Hardware-gated: requires an AR5212/AR5213/AR5413-family card whose EEPROM has:
- a target-power group with every testChannel == 0 (β
ee_numTargetPwr_11a/11b/11g == 0; the v3 parser computes counts by counting non-zero testChannels β ah_eeprom_v3.c:964-1002), or - a CTL with a non-zero index byte but an all-zero edge block (β
numEdges == 0inar5212GetMaxEdgePowerβ the rdEdge==0 break fires at i=0, ar5212_reset.c:2500-2505).
Both are plain zeroed/corrupted-EEPROM states β no precise crafting needed.
Root cause
void ar5212GetLowerUpperValues(uint16_t v, uint16_t *lp, uint16_t listSize, ...)
{
uint32_t target = v * EEP_SCALE;
uint16_t *ep = lp+listSize; /* == lp when listSize==0 */
...
if (target < (uint32_t)(lp[0] * EEP_SCALE - EEP_DELTA)) { /* UNINIT read: tempChannelList never filled */
if (target > (uint32_t)(ep[-1] * EEP_SCALE + EEP_DELTA)) { /* OOB read 2 bytes BELOW the array */
With listSize == 0 the caller's tempChannelList[] was never written (both callers fill it in a
loop bounded by the count), so lp[0] is uninitialized stack and ep[-1] reads one uint16 below
the array. The returned *vlo/*vhi (garbage or 0) then drive index selection:
ar5212GetMaxEdgePower:2510-2513: the "find lower index" loop never runs (numEdges==0), theHALASSERT(i != numEdges)is compiled out in production builds, andpRdEdgesPower[0].flag / twice_rdEdgePowerof a CTL that was declared empty decide the edge power β worst caseMAX_RATE_POWER(63) is programmed for the channel (regulatory-limit bypass on the radio), or a garbage-derived value.ar5212GetTargetPowers:2586-2608: ixlo/ixhi default to 0 β the zeroed (M_ZERO-allocated) EEPROM target powers are used β TX power collapses to 0.
Memory-safety exposure is confined to two stack reads (one uninitialized, one 2 bytes below a 16-byte local array); no disclosure to userspace and no write primitive β hence Low, AV:P, consistent with DF-1308/1309/3960.
Verified NOT vulnerable (pass-2 negative results for the checklist)
- (a) ctlEdges/regdomain walks:
ee_numCtlsis never raw EEPROM data β the v3 parser hardcodes {16,32} (ah_eeprom_v3.c:1147-1152) againstee_ctl[32]/ee_rdEdgesPower[8*32](ah_eeprom_v3.h:416,445); the v14/v4k/9287 backends computeee_numCtls = ibounded by AR5416(4K/9287)_NUM_CTLS with a sizeof-fit assert (ah_eeprom_v14.c:283-305 etc.). - target-power counts are computed (11aβ€8, 11gβ€3, 11bβ€2 β ah_eeprom_v3.c:939/947/955) and
tempChannelList[NUM_TEST_FREQUENCIES=8]can never overflow. - (b) GetGainBoundariesAndPdadcs does not exist in this file (RF backends ar2413/ar5413/ar2316/ ar2317/ar2425 own it) β out of scope here.
- (d) spur mitigation (1750-1922): all mask shifts provably β€ 31; binWidth assigned in every branch; callers guarantee OFDM (non-B) channels.
- ar5212CorrectGainDelta (2416-2486):
cckIndexandratesIndex[]clamps hold for every EEPROM-reachable value of ee_cckOfdmPwrDelta (0..255) and ee_cckOfdmGainDelta (0..255) β proved by range analysis;ah_pcdacTablewrites stay in [0,63]. - the
lp[1]past-the-end read in the walk loop is unreachable for both callers because target = freq100 β‘ 0 (mod 100) can never sit in the (last100-10, last*100+10) non-return band β the final iteration always returns at the "close" check. - FreeBSD/OpenBSD CVE family for this HAL (beacon/DMA/timer bugs) does not intersect this file's code paths; the EEPROM OOB family (DF-1308/1309/3960-era fixes) is bounded here as shown above.
VERDICT β DF-3977
status: inconclusive (hardware-gated; source-level certainty: certain for the ep[-1] read shape, likely for end-to-end reachability which additionally requires the zeroed-EEPROM condition)
Analysis performed
Read ar5212_reset.c end-to-end (2802 lines) plus the data-feeding layers: ah_eeprom_v3.c/h (target powers, CTL edges, spur channels, gain deltas), ah_eeprom_v14/v4k/9287 ctl backends, ar5212.h (ah_pcdacTable/ah_ratesArray), ar5212_attach.c (M_ZERO allocation), ah_osdep.c (ath_hal_malloc = kmalloc M_ZERO).
Every checklist item was driven to a conclusion (see README "Verified NOT vulnerable"). The single
surviving memory-safety defect is ar5212GetLowerUpperValues (ar5212_reset.c:2639/2643) reading
lp[0] (uninitialized stack β the caller's fill loop never ran) and ep[-1] (2 bytes below the
caller's tempChannelList[]) when listSize == 0. Reachable from:
ar5212GetTargetPowerswithee_numTargetPwr_11b/11g == 0(all testChannels zero in the EEPROM group β ah_eeprom_v3.c:973-999 counts only non-zero entries; ar5212_reset.c:2303/2218 pass the count unguarded), andar5212GetMaxEdgePowerwithnumEdges == 0(a CTL whose index byte is non-zero but whose edge block is all zeros β ar5212_reset.c:2500-2505).
Consequences: uninitialized stack values select the lower/upper "pier" channels; the subsequent
index walk in GetMaxEdgePower silently uses entry 0 of the empty CTL (HALASSERT is a no-op in
release), which can program MAX_RATE_POWER for the channel β a regulatory TX-power bypass β while
GetTargetPowers fails toward 0 power. No kernel-memory disclosure, no write primitive, no panic
expected (both reads land in the mapped kernel stack frame).
Why not reproduced
The QEMU guest has no Atheros NIC (env.txt: no 0x168c device, no ath module); the trigger data is in the card's serial EEPROM, read at if_ath attach. Following the DF-1308/1309/3960 convention (all rated Low, AV:P, hardware-gated) this is recorded inconclusive with source-level certainty.
Fix validation
not_testable (no hardware). fix.diff: early-out in both callers when the count is zero (fail-safe: MAX_RATE_POWER is already the initialized twiceMaxEdgePower; target powers zeroed by M_ZERO), plus a defensive listSize==0 guard in GetLowerUpperValues.
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
not_testablefix.diff authored (three guards); not runtime-testable without an Atheros NIC.
findings/poc/DF-3977/fix.diff
Confirmed kernel references
- sys/dev/netif/ath/ath_hal/ar5212/ar5212_reset.c:2639
- sys/dev/netif/ath/ath_hal/ar5212/ar5212_reset.c:2643
- sys/dev/netif/ath/ath_hal/ar5212/ar5212_reset.c:2500
- sys/dev/netif/ath/ath_hal/ar5212/ar5212_reset.c:2510
- sys/dev/netif/ath/ath_hal/ar5212/ar5212_reset.c:2579
- sys/dev/netif/ath/ath_hal/ah_eeprom_v3.c:973
- sys/dev/netif/ath/ath_hal/ah_eeprom_v3.c:964
Detail
Evidence (decisive lines)
README.md (reachable-empty-list conditions + the full negative-result list); VERDICT.md; fix.diff (three guards)
PoC changes
No PoC: hardware-gated (needs an AR5212-family card with zeroed EEPROM target-power/CTL blocks).
Verified recommended fix
Guard listSize==0 in ar5212GetLowerUpperValues and early-out in ar5212GetMaxEdgePower/ar5212GetTargetPowers when the pier list is empty.
Verdict
ar5212GetLowerUpperValues (ar5212_reset.c:2639/2643) dereferences lp[0] and ep[-1] before any listSize check; with the empty pier lists reachable from a zeroed/crafted EEPROM (ee_numTargetPwr_*==0 via ah_eeprom_v3.c:973-999, or a CTL with non-zero index and all-zero edges via ar5212_reset.c:2500-2505) this performs an uninitialized stack read and a 2-byte OOB stack read below the callers' tempChannelList[], and lets garbage select CTL edge power β worst case MAX_RATE_POWER (63) programmed for the channel (regulatory TX-power bypass). No disclosure/write primitive; Low, AV:P, consistent with the ar5416 EEPROM family (DF-1308/1309/3960). All other pass-2 checklist items for this file were driven to verified-negative conclusions (ctl walks bounded by construction in all EEPROM backends; gain-boundary code lives in the RF backends; spur-mitigation shifts provably in range; CorrectGainDelta clamps hold by range analysis).
No comments yet.