# DF-4648 — ar2316: unbounded `kk` write in ar2316getGainBoundariesAndPdadcsForPowers → heap OOB write past pcdacTable[128]

## Verdict: REPRODUCED (harness, verbatim kernel code) — impact: dos (hardware-gated), twin of DF-4619 (ar5413)

## Root cause

`sys/dev/netif/ath/ath_hal/ar5212/ar2316.c` —
`ar2316getGainBoundariesAndPdadcsForPowers()` writes the final PDADC table
through a `uint32_t kk` index into `pPDADCValues` in three unbounded loops:

- ar2316.c:454-458 — extrapolate-below loop (`pPDADCValues[kk++]`, iterations = `-ss`)
- ar2316.c:464-465 — mid loop (`pPDADCValues[kk++] = VpdTable_I[ii][ss++]`, iterations = `maxIndex`)
- ar2316.c:474-482 — extrapolate-above loop (`pPDADCValues[kk++]`, iterations = `tgtIndex - maxIndex`)

`pPDADCValues` is `ahp->ah_pcdacTable` (ar2316.c:512) = `priv->pcdacTable`,
a `uint16_t[PWR_TABLE_SIZE_2316=128]` member (ar2316.c:41,
PWR_TABLE_SIZE_2413=128 at ah_eeprom_v3.h:282) of the `ath_hal_malloc`ed
`struct ar2316State` (ar2316.c:733). No loop checks `kk < 128`; only the
tail backfill (ar2316.c:490-493) assumes `kk <= 128`.

Per-gain write count = `max(0,-ss) + tgtIndex` where
`ss = pPdGainBoundaries[ii-1] - Pmin_t2[ii] - pdGainOverlap_t2` and
`tgtIndex = pPdGainBoundaries[ii] + pdGainOverlap_t2 - Pmin_t2[ii]`.
`pPdGainBoundaries` is clamped to ≤63 (ar2316.c:436-441) but
`pdGainOverlap_t2` comes from the 4-bit `AR_PHY_TPCRG5_PD_GAIN_OVERLAP`
hardware field (ar2316.c:532-533; mask 0xF, ar5212phy.h:349-350) → 0..15,
and `Pmin_t2` is EEPROM-derived. Summed over 4 gains the algebra allows
kk up to `3 + 63 + 6*overlap` = 156 > 128 with realistic register values
(overlap 15), so up to 28 cells (56 bytes) past the array; the measured
walk with the crafted dataset below runs kk to 168 (40 cells / 80 bytes
past) into `Bank1Data`..`Bank7Data` (offsets verified: pcdacTable@72,
Bank1Data@328, struct size 2008) — an intra-object heap overflow of the
`ath_hal_malloc` allocation. The corrupted bank scratch is programmed to
the radio at the next `ar2316SetRfRegs` (ar2316.c:212-216).

## Trigger (threat model)

Crafted EEPROM on an AR2316-bearing card (physical / supply-chain
attacker): `xpd_mask=0xF` (4 used pdGains), pwr_t4 curves giving the
{0,58,58,58} Pmin pattern (pwr_t4[0]=4*pwr_I, +2*delta per point — the
exact shape ar2413SetupRawDataset/ar2413EepromToRawDataset produce,
ah_eeprom_v3.c:633-728), TPCRG5 overlap set to 15. First channel set
after attach invokes ar2316SetPowerTable (ar2316.c:501) → the kk walk
(ar2316.c:535-537 call, no return-value check beyond the compiled-out
HALASSERT at :538).

## How it was verified

Verbatim-extraction harness (`ar2316_harness.c` mode 1): the guest's
pristine `/usr/src` ar2316.c function bodies (lines 251-499) are
sed/awk-extracted at build time (see `build.sh`, anchor-based) and
compiled against a faithful `struct ar2316State` (member order from
ar2316.c:39-61, bank rows 1/1/1/29/3 counted from the shipped
ar5212.ini). HALASSERT compiled out to mirror the release build
(ah_internal.h:665-676). The bank region is pre-filled with 0xAAAA
canaries; mode 1 reports the first/last clobbered cell.

`run.log` (decisive lines):

```
mode=1 numPdGainsUsed(ret)=4 minPower=0x0000
DF-4648 REPRODUCED: pPDADCValues write walked past pcdacTable[127]: first
 clobbered u16 cell 0 bytes past array end, last 79 bytes past
 (value 0x007f) => kk ran to 168
  => intra-object heap OOB WRITE past pcdacTable[PWR_TABLE_SIZE_2316=128]
```

Reproduced twice (`run.log`, `run.2.log`) with identical results.

## Exploitation ceiling

Hardware-gated (needs attacker-supplied AR2316 card with crafted EEPROM).
The overflow stays inside the 2008-byte `struct ar2316State` allocation
(banks + vpd tables are the landing zone), values are constrained
(pdadc clamps 0/127, uint16 Vpd-derived). Realistic ceiling: corrupted
RF bank programming / radio malfunction / garbage tx power — a DoS-class
defect, not a uid0 route. No exfiltration channel.

## Fix validation

`fix.diff` bounds all three write loops with `kk < PWR_TABLE_SIZE_2316`
(mirrors the accepted DF-4619 fix). `fix_run.log` (PATCHED mode 1):
`DF-4648: NO overflow detected`, bank canaries intact. Modes 2/3 still
reproduce on this diff (it only touches the kk loops) — those are
DF-4649/DF-4650, fixed by their own diffs; the combined patch leaves all
three modes clean.

## 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.
