β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2073

ResetKeyCacheEntry MIC-clear path lacks runtime bounds check on entry+64 (HALASSERT inert in release)

Summary

ar5212ResetKeyCacheEntry (ar5212_keycache.c:86-93): computes micentry=entry+64 and writes MIC key registers AR_KEYTABLE_KEY0..3 when slot type==TKIP and IS_MIC_ENABLED(ah) (true by default ar5212_attach.c:260). Only bound is HALASSERT(micentry<halKeyCacheSize) at :89 but HALASSERT expands to NOTHING unless AH_ASSERT defined (ah_internal.h:675) which is LINT64-only not GENERIC. Sibling ar5212SetKeyCacheEntry:185 has genuine runtime if(entry+64>=halKeyCacheSize)return AH_FALSE; ResetKeyCacheEntry does not. Invariant normally holds (SetKeyCacheEntry prevents creating TKIP slot at E where E+64>=size) but reset trusts it without re-verifying. Trigger requires privileged HAL capability manipulation: disable MIC -> install high-index TKIP key (SetKeyCacheEntry check skipped) -> re-enable MIC -> reset key. Impact: OS_REG_WRITE zero to MMIO offsets past keycache aperture (0x8800+(E+64)*32 beyond entry 127). Best case ignored/unmapped; worst case corrupts adjacent MAC register or PCI bus fault -> panic (local DoS). No host-RAM corruption. AV:L/AC:H/PR:H, A:L. Fix: replace HALASSERT with runtime if(micentry>=halKeyCacheSize)return AH_FALSE mirroring SetKeyCacheEntry:185.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2073 Β· 7 files
FileTypeDescriptionSize
VERDICT.md verdict full narrative: source-trace, mechanism, fix, Phase 8 validation 3.7 KB ↓ raw
README.md readme original reviewer README with theoretical trigger sequence 1.1 KB ↓ raw
fix.diff suggested-fix git-apply-able: replace inert HALASSERT with runtime guard mirroring SetKeyCacheEntry:185 1.1 KB view raw
build.sh build-script pushes fix.diff, documents validated build path 947 B view raw
run.sh run-script reports HW-gated source-only status; confirms no ath HW 817 B view raw
env.txt environment guest uname, cc, HW-gate check, source-trace citations 1.9 KB view raw
fix_build_combined.log build-log FULL combined (DF-2073+DF-2074) nativekernel build, rc=0 -Werror 5.6 MB ↓ download
README.md readme original reviewer README with theoretical trigger sequence
↓ download raw

DF-2073 PoC β€” ResetKeyCacheEntry MIC-clear lacks runtime bounds check

Status

Speculative β€” not directly reachable from unprivileged userspace. Requires privileged HAL capability manipulation (disable MIC, install high-index TKIP key, re-enable MIC, then reset key).

Preconditions

  1. AR5212-class ath(4) NIC present
  2. GENERIC kernel (no AH_ASSERT β€” HALASSERT is inert)
  3. Privileged access to HAL capability control (disable/re-enable MIC)

Trigger sequence

  1. Disable MIC capability (clear AR_STA_ID1_CRPT_MIC_ENABLE)
  2. Install TKIP key at high index E where E < halKeyCacheSize but E+64 >= size (SetKeyCacheEntry:185 check is skipped when MIC disabled)
  3. Re-enable MIC
  4. Delete/reset the key β†’ ar5212ResetKeyCacheEntry executes OS_REG_WRITE to AR_KEYTABLE_KEY0(E+64) with E+64 >= size β†’ MMIO write past keycache aperture

Expected output

Best case: write ignored/unmapped (no visible effect). Worst case: kernel panic from PCI bus fault or adjacent register corruption.

Fix

Replace HALASSERT with runtime if (micentry >= halKeyCacheSize) return AH_FALSE, mirroring ar5212SetKeyCacheEntry:185.

VERDICT.md verdict full narrative: source-trace, mechanism, fix, Phase 8 validation
↓ download raw

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.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

VALIDATED build+boot. HW-gated.

NK_DONE rc=0; kernel #1 boots clean.
↓ fix.diffcombined kernel #1 rc=0 -Werror

Confirmed kernel references

Detail

Exploit chain

none (DoS/hardening).

Evidence (decisive lines)

HW-GATED (no AR5212 NIC). Source-CONFIRMED. ar5212ResetKeyCacheEntry HALASSERT(micentry<halKeyCacheSize) is inert on GENERIC (AH_ASSERT LINT64-only). Sibling guard at :185 is MIC-gated. MMIO write pas

Verified recommended fix

Replace inert HALASSERT with runtime guard returning AH_FALSE, mirroring SetKeyCacheEntry:185.

Verdict

HW-GATED (no AR5212 NIC). Source-CONFIRMED. ar5212ResetKeyCacheEntry HALASSERT(micentry<halKeyCacheSize) is inert on GENERIC (AH_ASSERT LINT64-only). Sibling guard at :185 is MIC-gated. MMIO write past keycache aperture.