# DF-4873 — ath_hal_9287EepromAttach error paths leak the malloc'd HAL_EEPROM_9287 (~1KB per failed attach; ar9287Attach's `bad:` path cannot reclaim it)

## What was verified (static, line-by-line)

1. **The allocation.** `sys/dev/netif/ath/ath_hal/ah_eeprom_9287.c:322-326`
   ```c
   ee = ath_hal_malloc(sizeof(HAL_EEPROM_9287));
   if (ee == AH_NULL)
       return HAL_ENOMEM;
   ```
   `ath_hal_malloc` = `kmalloc(size, M_ATH_HAL, M_INTWAIT | M_ZERO)`
   (`ah_osdep.c:121-124`). `sizeof(HAL_EEPROM_9287)` ≈ 1 KB (ee_base ~0x3d0
   bytes + `ee_rdEdgesPower[8*12]` = 96 × sizeof(RD_EDGES_POWER)).

2. **Three error returns after the allocation, none free it.**
   - mid-image read failure → `return HAL_EEREAD;` (ah_eeprom_9287.c:331-336)
   - bad checksum → `return HAL_EEBADSUM;` (ah_eeprom_9287.c:373-377)
   - bad EEPROM version → `return HAL_EEBADSUM;` (ah_eeprom_9287.c:392-396)

3. **Ownership is never transferred on those paths.** `AH_PRIVATE(ah)->ah_eeprom`
   and the `ah_eepromDetach`/`ah_eepromGet`/... ops are installed only at the
   very end (ah_eeprom_9287.c:400-406), after all of the error returns.

4. **The caller's unwind cannot reclaim it.** `ar9287Attach`
   (`sys/dev/netif/ath/ath_hal/ar9002/ar9287_attach.c:239-241`):
   ```c
   ecode = ath_hal_9287EepromAttach(ah);
   if (ecode != HAL_OK)
       goto bad;              /* ar9287_attach.c:362-367 */
   bad:
       if (ah != AH_NULL)
           ah->ah_detach(ah); /* == ar5416Detach (ar5416_attach.c:97) */
   ```
   `ar5416Detach` (`ar5416_attach.c:490-509`) calls `ath_hal_eepromDetach(ah)`
   at line 507, which is the macro
   ```c
   #define ath_hal_eepromDetach(_ah) do {                        \
       if (AH_PRIVATE(_ah)->ah_eepromDetach != AH_NULL)          \
           AH_PRIVATE(_ah)->ah_eepromDetach(_ah);                \
   } while (0)                                                   /* ah_internal.h:474-477 */
   ```
   — a no-op because `ah_eepromDetach` was never installed — then
   `ath_hal_free(ah)` frees only the `struct ath_hal_9287`, not `ee`.
   The ~1 KB `HAL_EEPROM_9287` is orphaned. **Leak confirmed.**

5. **Family bug.** `v14EepromAttach` has the identical shape
   (`ah_eeprom_v14.c:363` malloc; `return HAL_EEREAD` at ~:375 with no free);
   `v4k` mirrors it. This pack documents the ar9287 path (the audited file's
   call site); the v14/v4k twins should get the same `fail:` label.

## Trigger / threat model

Untrusted-EEPROM / hostile-hardware (physical access or resold/reprogrammed
card): any of the three failures is trivially produced by EEPROM content
(checksum mismatch being the easiest — the attacker recomputes or corrupts
it). Each attach attempt with such a card leaks ~1 KB permanently (M_ATH_HAL
kernel heap, no reference, never freed). Hotplug insert/remove cycles repeat
the leak without bound. No UAF, no corruption — a slow, permanent kernel
memory-exhaustion hygiene bug. Severity Low.

## Why no runtime PoC (untested, family precedent DF-4793/DF-4799/DF-4548)

The leak requires an AR9287 card whose EEPROM reads fail partway or fails
checksum/version — the QEMU audit guest has no Atheros NIC, so
`ar9287Attach` is never reached. The static ownership trace above is
complete and has no gaps (allocation → error return → caller unwind →
detach macro → nothing frees `ee`).

## Fix

`fail:` label freeing `ee` before each post-malloc error return — see
`fix.diff` (git-apply-able, verified with `git apply --check`). Not
guest-build-validated (no reason to build a kernel for a change whose
trigger hardware is absent; change is mechanical).

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