ar5212InvalidGainReadback reads rfBuf[4..5] past the 3-word RF bank 7 of 2413/2316/2317/5413/2425/2417 radios (gain-readback OOB read, UB)
Summary
The generic gain-step extraction indexes words 4-5 of bank 7, but every RF backend of that generation defines bank 7 with only 3 rows (ar5212.ini:1870/1878/1886/2158/2166) - only 5111 (8 rows) and 5112 (13, other branch) are big enough. Runs every long calibration cycle on 5GHz; on ar5413 the 8 bytes land inside vpdTable_L of the same malloc'd priv struct (C-array UB, no heap violation - RF gain misbehavior); dormant true-heap-OOB on ar2425/2417 (Bank7Data final member) gated off by 2.4GHz-only. Bounds demo verified on guest; pad-to-6-words fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5032 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 4.1 KB | β raw | |
| VERDICT.md | β | 2.8 KB | β raw | |
| oob_demo.c | β | 3.1 KB | view raw | |
| oob_demo.log | β | 1.2 KB | view raw | |
| build.sh | β | 66 B | view raw | |
| run.sh | β | 90 B | view raw | |
| env.txt | β | 197 B | view raw | |
| verdict.json | β | 3.5 KB | view raw | |
| manifest.json | β | 1.4 KB | view raw |
DF-5032 β AR5212 RF gain readback reads 2 words past the 3-word RF bank 7 (2413/2316/2317/5413/2425/2417 radios)
What
ar5212InvalidGainReadback() (ar5212_rfgain.c:123-154) takes the
non-5112/5111 branch for the newer AR5212-family radios and extracts the
6-bit gain step from RF bank 7:
gStep = ar5212GetRfField(ar5212GetRfBank(ah, 7), 6, 37, 0); /* :140 */
ar5212GetRfField() (ar5212_rfgain.c:92-120) walks the bank as bytes:
firstBit=37, numBits=6 β rfBuf[4] then rfBuf[5] (see the walk in
oob_demo.c, output in oob_demo.log).
But for these radios, bank 7 has only 3 rows in the shared ini tables (sys/dev/netif/ath/ath_hal/ar5212/ar5212.ini):
| radio | bank7 rows | struct holding it | what follows Bank7Data |
|---|---|---|---|
| ar5111 | 8 | ar5111.c:44 | (in bounds: 4,5 < 8) |
| ar5112 | 13 | ar5112.c:43 | (takes the IS_RAD5112_ANY branch β index 4 only) |
| ar2413 | 3 (ar5212.ini:1870) | ar2413.c:43 | vpdTable_L (intra-struct) |
| ar2316 | 3 (:1878) | ar2316.c | vpdTable_L (intra-struct) |
| ar5413 | 3 (:1886) | ar5413.c:43 | vpdTable_L (intra-struct) |
| ar2317 | 3 (:2158) | ar2317.c | vpdTable_L (intra-struct) |
| ar2425/2417 | 3 (:2166) | ar2425.c:44 | nothing β last member |
Reachability gate: the probe state is only armed on 5 GHz channels (or
5112+OFDM) β ar5212PerCalibrationN(), ar5212_reset.c:1085-1094 β
ar5212RequestRfgain(); the driver polls ath_hal_getrfgain() each
calibration tick (if_ath.c:5401) β ar5212GetRfgain() β
ar5212InvalidGainReadback().
Consequently:
- ar5413 (a/b/g, 5 GHz-capable, common card silicon): the OOB read is
live on every long calibration cycle on a 5 GHz channel. The 8 bytes
past
Bank7Data[3]land insidevpdTable_Lin the sameath_hal_malloc()'d RF private struct (ar5413.c:35-57,777) β undefined behavior / array OOB in C, but stays inside the allocation. - ar2425/ar2417:
Bank7Datais the final member ofar2425State(ar2425.c:36-45), so the same two reads would be a heap OOB read of 8 bytes past the allocation β but these radios are 2.4 GHz-only, the channel gate (ar5212_reset.c:1090) never arms the probe on them, so the path is dormant unless a 5 GHz channel is ever configured on such a part (e.g. by future band-capability changes). - ar2413/2316/2317: same struct shape as 5413 (vpd tables follow) and same 2.4 GHz-only gating.
Impact: gStep is garbage derived from adjacent fields β L2/L3/L4,
gv->loTrig/hiTrig (ar5212_rfgain.c:143-149) are wrong β the gain-ladder
adjust decisions (ar5212AdjustGain) misfire β RF gain calibration
misbehaves (link-quality degradation; worst case the radio walks the gain
ladder to a rail). The value never leaves the HAL; no disclosure or
corruption is reachable from it. Severity: Low (hardware-triggered,
no attacker control, functional impact).
Files
oob_demo.cβ replicatesar5212GetRfField's index walk (ar5212_rfgain.c:102-117) for all four call sites in the file and prints the touched word indices against the realN(ar5212Bank7_*)row counts.oob_demo.logβ its output on the audit guest (exit 1: 5 radios OOB).env.txtβ guest uname / cc.verdict.json/manifest.json.
Build / run (any UNIX with a C compiler; ran on the DragonFly guest)
cc -O2 -o oob_demo oob_demo.c && ./oob_demo
Expected
InvalidGainReadback/else GetRfField(bank7, 6, 37, 0) words touched: [4] [5] (max index 5) ... ar5413 (else path) N(ar5212Bank7_5413) = 3 rows -> index 5 *** OUT OF BOUNDS (reads 2 words past) ***
Kernel-level verification (panic/corruption) is intentionally skipped: Low severity, no memory-safety consequence beyond the intra-struct read on the reachable path, and no AR5412/5413 hardware exists on the audit guest.
Fix
Extend the bank-7 shadow arrays for the affected radios (or clamp the
read): simplest correct fix is to make ar5212InvalidGainReadback() use a
bank-7 field layout matching each radio family; minimal hardening is to
size Bank7Data[] to 6 words minimum in the 2413/2316/2317/5413/2425/2417
RF private structs so the byte-walk stays inside the array. See
verdict.json: recommended_fix.
DF-5032 VERDICT β AR5212 rfgain readback OOB read past 3-word bank 7
Status: untested (kernel impact), defect certain (static proof +
arithmetic demo run on the guest).
Reproduced or not, and why
- The bounds defect is proven statically and by the index-walk demo
(
oob_demo.log, run on the DragonFly guest):ar5212GetRfField(bank7, 6, 37, 0)β ar5212_rfgain.c:140 β touchesrfBuf[4]andrfBuf[5], while every non-5112/5111 RF backend's bank 7 shadow array has only 3 words (ar5212.ini:1870/1878/1886/2158/2166). - The kernel path was not executed: the audit guest has no Atheros
hardware (and no 5 GHz-capable 5413 card can exist in it), so
ar5212GetRfgain()cannot run. Per the audit contract, PoC/Phase-V work is skipped for Low-severity, hardware-only findings.
Reachability (path:line)
ar5212PerCalibrationN (ar5212_reset.c:1085-1094) arms the probe on 5 GHz channels β ar5212RequestRfgain (ar5212_rfgain.c:160-172) β driver polls ath_hal_getrfgain (if_ath.c:5401) β ar5212GetRfgain (ar5212_rfgain.c:279-333) β ar5212InvalidGainReadback:140 β OOB read. Radio gates:
- ar5413: 5 GHz-capable β live path on every calibration cycle; the
read lands in
vpdTable_Linside the same allocation (ar5413.c:35-57; priv fromath_hal_malloc, ar5413.c:777) β C-level array OOB, no heap violation. - ar2425/ar2417:
Bank7Datais the last member (ar2425.c:36-45) β would be an 8-byte heap OOB read β but 2.4 GHz-only radios never pass the 5 GHz gate (ar5212_reset.c:1090-1091). Dormant. - ar2413/2316/2317: vpd tables follow Bank7Data (ar2413.c:35-57); 2.4 GHz only β dormant, and intra-struct anyway.
Impact ceiling
gStep (garbage) β wrong loTrig/hiTrig (ar5212_rfgain.c:147-149) β
misfiring gain-ladder adjustments (ar5212AdjustGain, :187-237) β RF
performance degradation / potential link loss. The read value is never
exported (no info leak) and never used as an index/pointer (no corruption).
Low severity, CWE-125.
Fix sketch
Keep the byte-walk inside the shadow arrays: bump the affected RF structs'
Bank7Data[] to β₯6 words (harmless extra 12 bytes per radio priv), or gate
ar5212InvalidGainReadback's gStep extraction to radios whose bank-7 layout
actually places bits 33-43 there (5111 only, 8 rows) and treat the newer
radios as "never adjust" (gStep == 0x3f semantics already encode that).
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.
(No fix.diff for this item: documented recommendation only - see Fix above.)
Fix verification
not_testableFix validation not applicable: memory-corruption reproduction did not occur (no hardware), and the finding is Low severity / functional RF impact only.
n/a
Confirmed kernel references
- sys/dev/netif/ath/ath_hal/ar5212/ar5212_rfgain.c:140
- sys/dev/netif/ath/ath_hal/ar5212/ar5212_rfgain.c:102
- sys/dev/netif/ath/ath_hal/ar5212/ar5212.ini:1870
- sys/dev/netif/ath/ath_hal/ar5212/ar5212.ini:1886
- sys/dev/netif/ath/ath_hal/ar5212/ar5212.ini:2166
- sys/dev/netif/ath/ath_hal/ar5212/ar2425.c:36
- sys/dev/netif/ath/ath_hal/ar5212/ar5413.c:35
- sys/dev/netif/ath/ath_hal/ar5212/ar5212_reset.c:1090
Detail
Evidence (decisive lines)
oob_demo.log: 'InvalidGainReadback/else GetRfField(bank7, 6, 37, 0) words touched: [4] [5] (max index 5)' followed by 'ar5413 (else path) N(ar5212Bank7_5413) = 3 rows -> index 5 *** OUT OF BOUNDS (reads 2 words past) ***' (x5 radios), exit 1; static refs ar5212_rfgain.c:140 + ar5212.ini bank7 row counts
PoC changes
No prior PoC; oob_demo.c authored this pass (pure-arithmetic replica of ar5212GetRfField's index walk vs the real bank-7 row counts).
Verified recommended fix
Size Bank7Data[] >= 6 words in the 2413/2316/2317/5413/2425/2417 RF priv structs (keeps the generic byte-walk in bounds), or restrict the gStep extraction at ar5212_rfgain.c:140 to radios whose bank-7 layout actually holds bits 33-43 (RF5111).
Verdict
Bounds defect is certain: ar5212InvalidGainReadback (ar5212_rfgain.c:140) extracts gStep via ar5212GetRfField(bank7, 6, 37, 0), whose byte walk (ar5212_rfgain.c:102-117) reads rfBuf[4] and rfBuf[5], but the RF bank-7 shadow arrays of the 2413/2316/2317/5413/2425/2417 backends have only 3 words (ar5212.ini:1870,1878,1886,2158,2166; only 5111 has 8, 5112 has 13 and takes the other branch). Kernel-path execution was not possible: the probe is armed only on 5 GHz channels (ar5212_reset.c:1085-1094; driver polls ath_hal_getrfgain, if_ath.c:5401) and the audit guest has no Atheros hardware. Impact on the reachable path (ar5413, 5 GHz): the 8 bytes land in vpdTable_L inside the same ath_hal_malloc'd priv struct (ar5413.c:35-57) - C-level array OOB / UB, no heap violation, value never exported nor used as an index - so the practical effect is miscalculated loTrig/hiTrig (ar5212_rfgain.c:143-149) and misfiring gain-ladder adjustments: RF performance degradation only. Dormant variant: ar2425/2417 where Bank7Data is the final struct member (ar2425.c:36-45) would make the same read an 8-byte heap OOB read, gated off because those radios are 2.4 GHz-only. Severity Low; verification skipped per contract (hardware-only trigger). The index-walk arithmetic demo (oob_demo.c, byte-for-byte replica of the walk) ran on the DragonFly guest and prints the OOB for all five 3-row radios (oob_demo.log, exit 1).
No comments yet.