ar5211GetLowerUpperValues with empty pier list: 2-byte OOB stack read below tempChannelList plus uninitialized stack read -> garbage regulatory edge-power selection (DF-3977 ar5211 twin)
Summary
The helper evaluates *(pList + listSize - 1) before validating listSize; with listSize==0 this reads pList[-1] - 2 bytes below the caller's tempChannelList[8] - and then reads uninitialized *pList. Reachable via a matched CTL whose first edge is blanked (numEdges==0, tempChannelList untouched, call proceeds unconditionally). Garbage lower/upper channels select twiceMaxEdgePower outside the conformance limit -> TX power programmed above the regulatory edge limit. Primitive PROVEN on the guest (verbatim function + guard page: 3/3 SIGSEGV at the exact load; patched harness returns cleanly). Hardware-gated kernel manifestation. Same helper text as DF-3977 (ar5212) - separate file, separately fixable. Also notable: 7 sibling candidates killed with proof (pcdacTable cannot overflow - 6-bit values; the pList[i+1] one-past reads unreachable for integer-MHz targets; DF's mhz2ieee /20 formula protects chan2GHzData where FreeBSD's would overflow).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4141 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df4141_oob.c | β | 4.2 KB | view raw | |
| df4141_oob_fixed.c | β | 4.3 KB | view raw | |
| build.sh | β | 94 B | view raw | |
| run.sh | β | 103 B | view raw | |
| build.log | β | 9 B | view raw | |
| run.log | β | 310 B | view raw | |
| run.2.log | β | 310 B | view raw | |
| run.3.log | β | 310 B | view raw | |
| fix_run.log | β | 211 B | view raw | |
| env.txt | β | 122 B | view raw | |
| fix.diff | β | 1.9 KB | view raw | |
| VERDICT.md | β | 5.9 KB | β raw | |
| README.md | β | 2.6 KB | β raw |
DF-4141 β ar5211GetLowerUpperValues empty-pier-list OOB stack read (DF-3977 ar5211 twin)
File: sys/dev/netif/ath/ath_hal/ar5211/ar5211_reset.c
What
ar5211GetLowerUpperValues() (ar5211_reset.c:1813) loads
const uint16_t listEndValue = *(pList + listSize - 1); at :1818 before
any validation of listSize. When the caller passes listSize == 0, this
is a 2-byte read below the caller's array (pList[-1]), and the
subsequent first-element test at :1829 reads *pList, which is
uninitialized whenever the caller did not fill the array.
Kernel reachability (source-certain): ar5211SetRateTable()
(ar5211_reset.c:1528-1538) copies a CTL's RD_EDGES_POWER row into
uint16_t tempChannelList[NUM_EDGES] on the stack, stopping at the first
zero rdEdge; a matched CTL (ee_ctl[i] == cfgCtl != 0,
ar5211_reset.c:1357-1361) whose first edge is blanked
(rdEdge == 0 && twice_rdEdgePower == 0 β degenerate/corrupt/crafted
EEPROM, loaded by readEepromCTLInfo() in ah_eeprom_v3.c:1013) leaves
numEdges == 0 and tempChannelList untouched, then calls
ar5211GetLowerUpperValues(freq, tempChannelList, 0, ...) at :1537.
Impact: OOB/uninitialized stack values become lowerChannel/upperChannel,
selecting a twice_rdEdgePower (twiceMaxEdgePower, :1545-1550) that no
longer reflects the regulatory edge limit β TX power programmed above the
conformance-test limit for the channel. No kernel write primitive; this is
the ar5211 copy of the ar5212 defect filed as DF-3977 (the two HALs carry
textually identical helper code).
Build / Run (guest, no hardware needed β proof of primitive)
# build cc -O2 -Wall -o df4141_oob df4141_oob.c # run β SUCCESS = prints the SIGSEGV marker and exits 33 ./df4141_oob ; echo "exit=$?" # expect: exit=33
The harness places the array at the start of an mmap'd page whose
predecessor page is PROT_NONE, so the pList[-1] load at :1818 faults
deterministically. Runs: 3/3 faults (run.log, run.2.log, run.3.log).
Fix validation (harness-level): df4141_oob_fixed.c carries the verbatim
function with the exact guard from fix.diff β it returns cleanly with
lower=0 upper=0 (fix_run.log, exit=1 "NOT-FAULTED" branch by design).
fix.diff additionally git apply --checks cleanly against the sys/ tree
(read-only audit; never applied).
Kernel-level manifestation
Hardware-gated: the guest has no AR5211 NIC (0 atheros/prism PCI devices), so the in-kernel path cannot be triggered there. In-kernel effect would be a 2-byte stack OOB read + uninitialized stack read inside ar5211Reset() (the values never leave the HAL; they misprogram radio power).
DF-4141 VERDICT β ar5211GetLowerUpperValues empty-list OOB stack read
Status: inconclusive (kernel manifestation hardware-gated) β primitive PROVEN on guest (3/3 deterministic faults); defect existence certain at source level.
The defect (path:line)
sys/dev/netif/ath/ath_hal/ar5211/ar5211_reset.c
- :1818
const uint16_t listEndValue = *(pList + listSize - 1);β withlistSize == 0readspList[-1]: 2 bytes BELOW the caller's array. In the kernel this is a stack OOB read belowtempChannelList[NUM_EDGES](declared :1510 inar5211SetRateTable). - :1829
if (target < (uint32_t)(*pList * EEP_SCALE - EEP_DELTA))β readstempChannelList[0], uninitialized whennumEdges == 0(the fill loop at :1530-1534 wrote nothing). - Reachability: :1530-1538 β
numEdges = iafter the break-on-zero-rdEdge loop; a matched CTL (matched at :1357-1361,ee_ctl[i] != 0) whose whole edge row is blank yieldsnumEdges == 0and the call at :1537 proceeds unconditionally.ee_rdEdgesPowerrows are populated from EEPROM byreadEepromCTLInfo()(ah_eeprom_v3.c:1013-1104); a blank first pair (rdEdge == 0 && twice_rdEdgePower == 0) survives the fbin2freq pass at :1090-1099 untouched. - Consequence:
lowerChannel/upperChannelbecome OOB/uninit stack values;twiceMaxEdgePower(:1545-1550) can then be taken frompRdEdgesPower[0].twice_rdEdgePowerinstead of the correct edge row, or left at the default 63 (63 = 31.5 dBm half-dBm units β above most regulatory limits) βpRatesPower[i] = AH_MIN(twicePower, twiceMaxEdgePower ...)(:1633) no longer bounds to the conformance limit.
This is the ar5211 twin of DF-3977 (ar5212, same helper text) β a separate file, separately fixable.
Sibling defects considered and KILLED (why this pack covers only the empty-list read)
pList[i + 1]at :1861 reads one-past wheni == listSize-1. Proven unreachable for every caller:valueis always an integer MHz frequency (or integer pcdac), sotarget = value*EEP_SCALEis a multiple of 100; pier values are integers, so|pList[i]*100 - target|is a multiple of 100 and the near-match test (< EEP_DELTA=10) fires exactly on equality. Reaching the final iteration requirestarget >= pList[listSize-1]*100-10while the early return at :1839 excludestarget > listEnd*100+10, and the between-test ati = listSize-2returns for anything belowlistEnd*100-10β leaving onlytarget == listEnd*100, which the near-match catches. (DF'sieee80211_mhz2ieeeand the HAL both produce integer frequencies, verified netproto/802_11/wlan/ieee80211.c:1045, ah.c:1427.)ar5211GetLowerUpperPcdacsone-past-end walk (:1883-1889, find-loop exits withpChannelDataone past the array): only reachable ifar5211GetLowerUpperValuesreturned 0 for both bounds β same unreachable fall-through as above; withlistSize >= 1every return path yields real pier values which exist 1:1 inee_dataPerChannel11x(channelValue is assigned from the same array, ah_eeprom_v3.c:868). Killed.pcdacTable[64]overflow inar5211SetPowerTable(:1449-1475): PwrValues are 6-bit EEPROM fields (POWER_MASK 0x3f, ah_eeprom_v3.h:150) scaled by PWR_STEP=50 β max 3150 = PWR_MAX; interpolation is a convex combination; total table writes β€ 3150/50+1 = 64 = PWR_TABLE_SIZE exactly. Killed.tempChannelList[NUM_EDGES=8]target-power fill (:1554-1555):numChannels=ee_numTargetPwr_11aβ€ NUM_TEST_FREQUENCIES = 8 = NUM_EDGES (ah_eeprom_v3.c:939, :964-1002 counter increments only inside the nchan-bounded loop). Exactly fits. Killed.- CTL walk
&ee->ee_rdEdgesPower[i * NUM_EDGES](:1357-1359):ee_numCtlsis the constant NUM_CTLS(16)/NUM_CTLS_3_3(32) (ah_eeprom_v3.c:1147-1150), array is NUM_CTLS_MAX*NUM_EDGES. Killed. chan2GHzData[chanIEEE + 19]inar5211SetChannel(:791-793): DF's mhz2ieee for >2484 MHz is15 + (freq-2512)/20(ieee80211.c:1045) and ath_hal_mhz2ieee_2ghz matches (ah.c:1435) β ic_ieee β€ 26 β index β€ 45 = last row (46-entry table, ar5211_reset.c:49-97). (FreeBSD's (freq-2511)/5 mapping would overflow here; DragonFly does not use it.) Killed.tpcScaleReductionTable[5]indexah_tpScale(:1639): clampedsetting <= HAL_TP_SCALE_MIN(ah.c:808). Killed.- gain-ladder walks (:2033-2068):
currStepNumbounded by 0 and numStepsInLadder-1 guards. Killed. - IQ-cal division (:674-683): guarded
iCoffDenom != 0 && qCoffDenom != 0. Killed. (DF-2198 was ar5416's different formula.)
What ran on the guest
df4141_oob.c: verbatim copy of ar5211GetLowerUpperValues + guard-page placement. Build clean (cc 8.3). 3/3 runs SIGSEGV at the :1818 load, exit 33 (run.log, run.2.log, run.3.log).df4141_oob_fixed.c: verbatim function with the exact guard from fix.diff β clean return, lower=0/upper=0, no fault (fix_run.log).fix.diffgit apply --checkclean against sys/ (not applied; sys/ is read-only for the audit).
Why inconclusive (not reproduced/not_reproduced)
The QEMU guest has zero Atheros/Prism PCI devices (checked via pciconf), so
the in-kernel path (ar5211Reset β ar5211SetTransmitPower β
ar5211SetRateTable) cannot execute. The memory-safety primitive itself is
proven deterministically; the kernel-level manifestation (garbage
twiceMaxEdgePower β regulatory over-power programming) is established by
code reading only. No uid0/panic/leak claim is made; no exploit chain
exists beyond wrong radio configuration.
Recommended fix: guard listSize == 0 in ar5211GetLowerUpperValues and
skip the edge-interpolation block when numEdges == 0 (fix.diff does both).
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
fixedHarness-level fix validation: df4141_oob_fixed.c embeds the verbatim function with exactly the fix.diff guard; baseline faults 3/3 (exit 33) while the patched harness returns cleanly (lower=0 upper=0, no fault) β the OOB load is gone. fix.diff git-apply-checks cleanly against sys/ (never applied; sys/ is read-only). Kernel-level rebuild+retest not possible: no AR5211 NIC on the guest to drive ar5211SetRateTable; the guard introduces no new symbols and is C89-safe.
fix_run.log (patched: NOT-FAULTED, exit 1) vs run.log/run.2.log/run.3.log (baseline: FAULTED, exit 33); fix.diff; git apply --check output in VERDICT.md
Confirmed kernel references
- sys/dev/netif/ath/ath_hal/ar5211/ar5211_reset.c:1818
- sys/dev/netif/ath/ath_hal/ar5211/ar5211_reset.c:1829
- sys/dev/netif/ath/ath_hal/ar5211/ar5211_reset.c:1510
- sys/dev/netif/ath/ath_hal/ar5211/ar5211_reset.c:1537
- sys/dev/netif/ath/ath_hal/ar5211/ar5211_reset.c:1545
- sys/dev/netif/ath/ath_hal/ah_eeprom_v3.c:1090
Detail
Evidence (decisive lines)
run.log/.2/.3 (exit 33 + fault marker 3/3), fix_run.log (patched guard -> clean return lower=0 upper=0), df4141_oob.c (verbatim function), VERDICT.md (full trace + killed sibling candidates), fix.diff (git apply --check clean, not applied)
PoC changes
n/a (harness authored fresh; verbatim function copy, no seed)
Verified recommended fix
Guard listSize==0 in ar5211GetLowerUpperValues (listEndValue ternary + early return) and skip the edge block when numEdges==0 in ar5211SetRateTable
Verdict
Defect certain at source level and primitive PROVEN on the guest: a verbatim copy of ar5211GetLowerUpperValues() faults deterministically (3/3 runs, SIGSEGV at the :1818 *(pList + listSize - 1) load with listSize==0, exit 33) when the pier array sits at a guard page. Kernel reachability is source-certain (ar5211SetRateTable:1530-1537 passes numEdges==0 whenever a matched EEPROM CTL row begins with a blanked edge; readEepromCTLInfo preserves such rows), but the QEMU guest has no AR5211 hardware, so the in-kernel manifestation (2-byte stack OOB read below tempChannelList[8] + uninitialized stack read, feeding garbage twiceMaxEdgePower -> regulatory over-power radio programming) could not be executed. No kernel write primitive, no leak to userspace; impact ceiling is wrong TX-power programming / undefined-behavior stack reads inside ar5211Reset. ar5211 twin of DF-3977.
No comments yet.