# DF-4913 VERDICT — REPRODUCED (kernel memory leak)

## Finding

`ath_hal_v14EepromAttach()` (sys/dev/netif/ath/ath_hal/ah_eeprom_v14.c:333-446)
mallocs `HAL_EEPROM_v14` at :363 (`ath_hal_malloc` → `kmalloc(M_ATH_HAL,
M_INTWAIT|M_ZERO)`, ah_osdep.c:118-123; 4800 bytes on this build) and has
three post-malloc error returns that neither free the object nor install it:

* :376 `return HAL_EEREAD;` — mid-image EEPROM read failure
* :414 `return HAL_EEBADSUM;` — XOR checksum != 0xffff
* :433 `return HAL_EEBADSUM;` — EEPROM version nibble != 14

The eeprom becomes reachable only at :438-440
(`AH_PRIVATE(ah)->ah_eeprom = ee`, `ah_eepromDetach = v14EepromDetach`), after
every error return. The callers (ar5416_attach.c:373, ar9280_attach.c:256,
ar9160_attach.c:194, ar9130_attach.c:170) `goto bad` → `ar5416Detach()`
(ar5416_attach.c:493-509) → `ath_hal_eepromDetach` macro (ah_internal.h:474-478)
which is a **no-op** when `ah_eepromDetach` was never installed → the 4800-byte
block is permanently orphaned. This is the exact twin of DF-4873
(ar9287, ah_eeprom_9287.c) — DF-4873's verdict already named this file as
having the identical family bug.

## Reproduction (2026-09-24, guest DragonFly 6.5-DEVELOPMENT #0 X86_64_GENERIC)

KLD PoC `v14leak.c` calls the real in-kernel `ath_hal_v14EepromAttach()`
(ath_hal compiled into the guest kernel; symbol verified via
`nm /boot/kernel/kernel`) with a synthetic zeroed `struct ath_hal_private`
whose `ah_eepromRead` callback serves crafted EEPROM images:

| mode            | trigger                                   | return       | orphans |
|-----------------|-------------------------------------------|--------------|---------|
| EEREAD@376      | read callback fails at image word 5       | HAL_EEREAD   | 200/200 |
| EEBADSUM@414    | XOR checksum != 0xffff                    | HAL_EEBADSUM | 200/200 |
| BADVER@433      | checksum-valid, version nibble 0          | HAL_EEBADSUM | 200/200 |
| control         | fully valid image (version 0xE001)        | HAL_OK       | 0 (detach frees) |

Result (`vmstat -m`, malloc type `ath_hal`):

* run 1: 0 blocks → **600 blocks / 2.93M** after trigger; **unchanged after
  `kldunload`** (permanent).
* run 2: 600 → **1200 blocks / 5.86M** (leak accumulates per attach round).
* control contributed 200 transient allocations that were all freed via the
  installed `v14EepromDetach` (inuse unchanged by control).

See run.log / console.log / boot.log serial output in this directory.

## Impact ceiling

Unbounded kernel-heap exhaustion: ~4.8 KB per failed attach. Trigger content is
attacker-chosen on the untrusted-EEPROM model (corrupt/reprogrammed EEPROM or
hostile card); every attach attempt fails the same way. Repeatable without
reboot via hotplug / devd re-probe / driver reload cycles — same model as
DF-4873 (physical access or crafted hardware). Not remotely triggerable, not
a memory-safety corruption: **impact = kernel memory leak (DoS by exhaustion),
Low severity**.

## Why it is not worse

The orphaned block contains no pointers INTO the kernel that get reused; it is
pure memory loss. No UAF, no info leak (block never copied out), no overflow.

## Fix

`fix.diff` in this directory: `ath_hal_free(ee)` before each of the three
error returns (mirrors the DF-4873 family fix). Fix validation would require
rebuilding the guest kernel (ath_hal is compiled in, module symbol shadowing
does not apply) — not performed; the fix is mechanical and matches the
verified baseline behavior (control path proves free-on-error is balanced by
the installed detach path).

## PoC changes vs the initial seed

The initial draft auto-ran the demo at MOD_LOAD and passed the handler
function directly to `DECLARE_MODULE()`; DFly requires a `moduledata_t`
(kern_linker.c casts `md->md_data` to `moduledata_t*` and reads `->name`).
Fixed by declaring `static moduledata_t v14leak_mod = {"v14leak",
v14leak_modevent, NULL}` and triggering the demo via `sysctl hw.v14leak=1`.
Draft also lacked the `-I/usr/obj/usr/src/sys/X86_64_GENERIC` include for the
generated kernel headers (device_if.h et al.).

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