# 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);`
  → with `listSize == 0` reads `pList[-1]`: 2 bytes BELOW the caller's
  array. In the kernel this is a stack OOB read below
  `tempChannelList[NUM_EDGES]` (declared :1510 in `ar5211SetRateTable`).
- :1829 `if (target < (uint32_t)(*pList * EEP_SCALE - EEP_DELTA))`
  → reads `tempChannelList[0]`, uninitialized when `numEdges == 0`
  (the fill loop at :1530-1534 wrote nothing).
- Reachability: :1530-1538 — `numEdges = i` after the break-on-zero-rdEdge
  loop; a matched CTL (matched at :1357-1361, `ee_ctl[i] != 0`) whose whole
  edge row is blank yields `numEdges == 0` and the call at :1537 proceeds
  unconditionally. `ee_rdEdgesPower` rows are populated from EEPROM by
  `readEepromCTLInfo()` (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/upperChannel` become OOB/uninit stack values;
  `twiceMaxEdgePower` (:1545-1550) can then be taken from
  `pRdEdgesPower[0].twice_rdEdgePower` instead 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 when `i == listSize-1`. Proven
  unreachable for every caller: `value` is always an integer MHz frequency
  (or integer pcdac), so `target = value*EEP_SCALE` is 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 requires `target >= pList[listSize-1]*100-10`
  while the early return at :1839 excludes `target > listEnd*100+10`, and
  the between-test at `i = listSize-2` returns for anything below
  `listEnd*100-10` — leaving only `target == listEnd*100`, which the
  near-match catches. (DF's `ieee80211_mhz2ieee` and the HAL both produce
  integer frequencies, verified netproto/802_11/wlan/ieee80211.c:1045,
  ah.c:1427.)
- `ar5211GetLowerUpperPcdacs` one-past-end walk (:1883-1889, find-loop
  exits with `pChannelData` one past the array): only reachable if
  `ar5211GetLowerUpperValues` returned 0 for both bounds — same unreachable
  fall-through as above; with `listSize >= 1` every return path yields real
  pier values which exist 1:1 in `ee_dataPerChannel11x` (channelValue is
  assigned from the same array, ah_eeprom_v3.c:868). Killed.
- `pcdacTable[64]` overflow in `ar5211SetPowerTable` (: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_numCtls` is 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]` in `ar5211SetChannel` (:791-793): DF's
  mhz2ieee for >2484 MHz is `15 + (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]` index `ah_tpScale` (:1639): clamped
  `setting <= HAL_TP_SCALE_MIN` (ah.c:808). Killed.
- gain-ladder walks (:2033-2068): `currStepNum` bounded 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.diff` `git apply --check` clean 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.
