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

ath_hal_v14EepromAttach leaks the malloc'd HAL_EEPROM_v14 (~4.8KB, M_ATH_HAL) on all three post-malloc error returns β€” DF-4873 (ar9287) family twin

Summary

Malloc at :363, three error returns (read fail/checksum/version) before the ownership install at :438-440 - the caller's eepromDetach no-op can't reclaim. VERIFIED on guest against the REAL in-kernel function with synthetic EEPROM images: 3 failure modes x 200 attaches orphaned 600 blocks/2.93MB (permanent across kldunload, stacking on re-run) while the valid control left inuse unchanged. Untrusted-EEPROM/hotplug-repeatable exhaustion. Free-on-error fix (+ the v4k sibling).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4913 Β· 12 files
FileTypeDescriptionSize
v14leak.c β€” 5.5 KB view raw
Makefile β€” 193 B ↓ download
build.sh β€” 79 B view raw
run.sh β€” 199 B view raw
build.log β€” 5.9 KB view raw
run.log β€” 457 B view raw
console.log β€” 1.2 KB view raw
env.txt β€” 421 B view raw
VERDICT.md β€” 4.4 KB ↓ raw
fix.diff β€” 873 B view raw
verdict.json β€” 4.2 KB view raw
manifest.json β€” 888 B view raw
VERDICT.md
↓ download raw

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.

Fix verification

not_testable
baseline reproduced→ patch + rebuild →patched clean

Fix validation not performed: ath_hal is compiled into the guest kernel, so validating fix.diff requires a full kernel rebuild; the finding is a pure leak (no corruption) for which fix validation is not mandated. The control path of the PoC (valid image -> attach succeeds -> detach frees) demonstrates the free path is balanced, and fix.diff adds the same ath_hal_free(ee) on the three error returns.

fix.diff mirrors the verified control-path lifetime (malloc at :363 <-> v14EepromDetach free at :318).
↓ fix.diffper-fix-DF-4913

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (vmstat -m ath_hal 0 -> 600 blocks/2.93M, unchanged after kldunload; second run 1200/5.86M), console.log (per-mode '200 attaches, 0 HAL_OK, 200 with ah_eeprom==NULL (orphaned mallocs)' + balanced control), build.log, env.txt, fix.diff

PoC changes

Initial draft auto-ran the demo at MOD_LOAD and passed the handler function directly to DECLARE_MODULE(); DFly requires a moduledata_t struct (kern_linker.c casts md_data to moduledata_t* and dereferences ->name, which made the first loads GPF in module_register/strcmp). Fixed by declaring static moduledata_t v14leak_mod and moving the demo behind sysctl hw.v14leak=1; added -I/usr/obj/usr/src/sys/X86_64_GENERIC for generated kernel headers.

Verified recommended fix

ath_hal_free(ee) before each of the three post-malloc error returns (HAL_EEREAD :376, HAL_EEBADSUM :414, HAL_EEBADSUM :433) β€” same fix as DF-4873.

Verdict

Kernel heap memory leak (permanent, 4800 bytes per failed attach) on the v14 EEPROM-attach error paths of ath_hal_v14EepromAttach: the HAL_EEPROM_v14 malloc'd at ah_eeprom_v14.c:363 is orphaned by three post-malloc returns β€” HAL_EEREAD at :376 (mid-image read failure), HAL_EEBADSUM at :414 (bad checksum) and :433 (bad version) β€” none of which free it, and AH_PRIVATE(ah)->ah_eeprom / ah_eepromDetach are installed only at :438-440 after every error return. The caller unwind (ar5416/ar9280/ar9160/ar9130 attach -> bad: -> ar5416Detach -> ath_hal_eepromDetach macro, ah_internal.h:474-478) is a no-op for it. Exact twin of DF-4873 (ar9287), which already predicted this file has the same family bug. Reproduced on the guest by calling the real in-kernel function with a synthetic ath_hal whose eepromRead callback serves crafted images: 3 failure modes x 200 attaches orphaned 600 blocks / 2.93MB of malloc type ath_hal (permanent across kldunload), while 200 valid attach+detach control cycles left inuse unchanged; a second run stacked to 1200 blocks / 5.86MB. Attacker model: untrusted EEPROM / hostile card (physical access or hotplug) with corrupt checksum/version β€” every attach fails identically, repeatable without bound.