# DF-2073 — ar5212 ResetKeyCacheEntry MIC-clear path lacks runtime bounds check

## Verdict
REPRODUCED (source-only confirmation; HW-gated — no AR5212 ath(4) NIC on guest).

## Severity / impact
Low. Realistic ceiling: local DoS via MMIO write past the keycache aperture
(0x8800+(E+64)*32, E up to 127 → 0x9FE0 vs aperture end 0x9800). Best case the
write is ignored/unmapped; worst case it corrupts an adjacent MAC register or
raises a PCI bus fault → kernel panic. No host-RAM corruption, no escalation
primitive — pure DoS/hardening gap.

## Mechanism (cited path confirmed line-by-line)
`ar5212ResetKeyCacheEntry(ah, entry)` (`sys/dev/netif/ath/ath_hal/ar5212/ar5212_keycache.c:65-97`):
1. Runtime-bounds-checks `entry < halKeyCacheSize` at :70 (good).
2. Reads `keyType = OS_REG_READ(AR_KEYTABLE_TYPE(entry))` at :75.
3. If `keyType == AR_KEYTABLE_TYPE_TKIP && IS_MIC_ENABLED(ah)` (:86):
   - `micentry = entry + 64` (:87).
   - **`HALASSERT(micentry < halKeyCacheSize)` at :89 is the only guard.**
   - `OS_REG_WRITE(AR_KEYTABLE_KEY0..3(micentry), 0)` at :90-93.

The HALASSERT is INERT on GENERIC:
- `sys/dev/netif/ath/ath_hal/ah_internal.h:665-676` — `#ifdef AH_ASSERT`
  expands to the assert; `#else` (line 675) `#define HALASSERT(_x)` → NOTHING.
- `options AH_ASSERT` appears ONLY in `sys/config/LINT64:1104`, NOT in
  `sys/config/X86_64_GENERIC`. So on the default kernel, the assert is gone.

The sibling `ar5212SetKeyCacheEntry` (`ar5212_keycache.c:153-190`) DOES carry a
real runtime guard at :185 — `if (IS_MIC_ENABLED(ah) && entry+64 >=
pCap->halKeyCacheSize) return AH_FALSE;` — but it is gated on
`IS_MIC_ENABLED(ah)`. So the invariant "no TKIP slot at E where E+64>=size"
only holds while MIC is enabled. `ar5212ResetKeyCacheEntry` trusts the invariant
without re-verifying, and the trust is backed only by the inert HALASSERT.

## Trigger (theoretical; requires privileged HAL capability control)
1. Disable MIC capability (clear `AR_STA_ID1_CRPT_MIC_ENABLE`).
2. Install a TKIP key at high index E with E < halKeyCacheSize but E+64 >= size
   (SetKeyCacheEntry:185 check is skipped because MIC is disabled).
3. Re-enable MIC.
4. Delete/reset the key → `ar5212ResetKeyCacheEntry` runs the MIC-clear branch
   → `OS_REG_WRITE` to `AR_KEYTABLE_KEY0(E+64)` where `E+64 >= 128` → MMIO
   offset 0x9FE0+ past the 0x9800 aperture (ar5212reg.h:334: `AR_KEYTABLE(_n) =
   0x8800 + _n*32`).

Not reachable from unprivileged userspace — requires privileged HAL capability
manipulation AND an AR5212-class NIC. This guest has no such NIC
(`pciconf -l | grep ath` → no match), so runtime confirmation is impossible.

## Fix
Replace the inert `HALASSERT` with a runtime guard mirroring
`ar5212SetKeyCacheEntry:185`, returning `AH_FALSE` (the existing error path)
when `micentry >= halKeyCacheSize`. See `fix.diff`.

The fix matches the finding markdown's `## Recommended fix` proposal verbatim
(replace HALASSERT with `if (micentry >= halKeyCacheSize) return AH_FALSE`).

## Fix validation (Phase 8)
- `fix.diff` applied to `/usr/src` (combined with DF-2074's fix).
- `make -j6 nativekernel KERNCONF=X86_64_GENERIC` → **rc=0, -Werror clean**
  (no errors, no warnings). `kernel.stripped` (15.7 MB) + `kernel.debug`
  (119 MB) produced.
- Installed to `/boot/kernel/kernel`; rebooted → `kern.version` bumped
  `#0` → `#1` (Sat Jul 25 11:53:22 UTC 2026); guest boots healthy, no
  regression.
- Runtime before/after NOT possible (HW absent) → `fix_status: not_testable`
  at runtime, but build-validated + source-traced to close the path.

## PoC changes
No source PoC to build/run (HW-gated). The README documents the theoretical
trigger. Evidence pack carries the source-trace citations, the `fix.diff`, the
combined build log, and the environment record.
