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)
PoC verification
Evidence pack
findings/poc/DF-4913 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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_testableFix 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).
Confirmed kernel references
- sys/dev/netif/ath/ath_hal/ah_eeprom_v14.c:363
- sys/dev/netif/ath/ath_hal/ah_eeprom_v14.c:376
- sys/dev/netif/ath/ath_hal/ah_eeprom_v14.c:414
- sys/dev/netif/ath/ath_hal/ah_eeprom_v14.c:433
- sys/dev/netif/ath/ath_hal/ah_eeprom_v14.c:438
- sys/dev/netif/ath/ath_hal/ah_internal.h:474
- sys/dev/netif/ath/ath_hal/ar5416/ar5416_attach.c:373
- sys/dev/netif/ath/ath_hal/ar9002/ar9280_attach.c:256
- sys/dev/netif/ath/ath_hal/ah_osdep.c:118
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.
No comments yet.