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

ath_hal_v4kEepromAttach leaks the malloc'd HAL_EEPROM_v4k (1152 B, M_ATH_HAL) on all three post-malloc error returns β€” third and final member of the eeprom-attach leak family (DF-4873 ar9287 / DF-4913 v14 / DF-1995 ar5210), verified live

Summary

Malloc at :311, three non-freeing error returns (read/checksum/version) before the install at :390-396 - the caller's detach-based unwind is a no-op. KLD harness drove the REAL in-kernel symbol: 3x200 failed attaches -> 600 blocks/675K PERMANENT across kldunload (stacking to 1.32M on round 2); the valid control perfectly balanced. Family COMPLETE with this filing. 3x ath_hal_free fix (git-apply clean).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4956 Β· 13 files
FileTypeDescriptionSize
v4kleak.c β€” 6.1 KB view raw
Makefile β€” 193 B ↓ download
build.sh β€” 150 B view raw
run.sh β€” 144 B view raw
build.log β€” 6.0 KB view raw
run.log β€” 414 B view raw
run.2.log β€” 164 B view raw
console.log β€” 1.0 KB view raw
env.txt β€” 264 B view raw
VERDICT.md β€” 6.0 KB ↓ raw
verdict.json β€” 4.6 KB view raw
fix.diff β€” 664 B view raw
README.md β€” 2.8 KB ↓ raw

DF-4956 β€” ath_hal_v4kEepromAttach error-path leak (eeprom-attach family, 3rd twin)

What

ath_hal_v4kEepromAttach() (sys/dev/netif/ath/ath_hal/ah_eeprom_v4k.c:282-399) mallocs HAL_EEPROM_v4k (1152 bytes, M_ATH_HAL, zeroed β€” ah_osdep.c:121-124) at :311 and has three post-malloc error returns that neither free the object nor install it:

  • :324 return HAL_EEREAD; β€” mid-image EEPROM read failure
  • :366 return HAL_EEBADSUM; β€” XOR checksum != 0xffff
  • :385 return HAL_EEBADSUM; β€” EEPROM version nibble != AR5416_EEP_VER (14)

The eeprom only becomes reachable at :390-396 (AH_PRIVATE(ah)->ah_eeprom = ee, ah_eepromDetach = v4kEepromDetach), after every error return. The caller (ar9285_attach.c:246-248 goto bad β†’ ah->ah_detach β†’ ar5416Detach (ar5416/ar5416_attach.c:490-508) β†’ ath_hal_eepromDetach macro (ah_internal.h:474-477)) is a no-op when ah_eepromDetach was never installed β†’ the 1152-byte block is permanently orphaned.

Third and final member of the family: DF-4873 (ar9287, ~1KB), DF-4913 (v14, 4.8KB, verified live), DF-1995 (ar5210 v1). Both prior verdicts predicted this file carries the same bug.

Build (on the DragonFly guest, as root, in this directory)

./build.sh        # make - builds v4kleak.ko KLD against the running kernel

Requires the kernel source tree at /usr/src and built kernel headers at /usr/obj/usr/src/sys/X86_64_GENERIC (both present on the audit guest). ath_hal_v4kEepromAttach is compiled into the guest kernel (nm /boot/kernel/kernel β†’ ffffffff8034f1a0 T ath_hal_v4kEepromAttach).

Run (as root)

./run.sh

Expected output

--- vmstat -m ath_hal BEFORE ---
(no ath_hal line β€” 0 in-use blocks)
...
            ath_hal     600    675K       0    390M      800      <- after trigger
            ath_hal     600    675K       0    390M      800      <- after kldunload (unchanged: permanent)

Serial console (vm.sh log):

DF-4956 PoC loaded (inert): sizeof(HAL_EEPROM_v4k)=1152 sizeof(ar5416eeprom_4k)=376 NWORDS=188 owl_eep_start_loc=64
DF-4956[EEREAD@324]:   200 attaches, 0 HAL_OK, 200 with ah_eeprom==NULL (orphaned mallocs)
DF-4956[EEBADSUM@366]: 200 attaches, 0 HAL_OK, 200 with ah_eeprom==NULL (orphaned mallocs)
DF-4956[BADVER@385]:   200 attaches, 0 HAL_OK, 200 with ah_eeprom==NULL (orphaned mallocs)
DF-4956[control]:      200 attaches, 200 HAL_OK, each detached (balanced; expect no ath_hal growth)

Second trigger round stacks: 600 blocks/675K β†’ 1.17K blocks/1.32M (run.2.log). Success criterion: β‰₯1 failed attach mode orphans its block permanently (vmstat ath_hal in-use grows by exactly NFAIL per mode and survives kldunload), while the valid-image control leaves in-use unchanged.

Fix

fix.diff: ath_hal_free(ee) before each of the three error returns β€” identical to the DF-4873/DF-4913 family fix.

VERDICT.md
↓ download raw

DF-4956 VERDICT β€” REPRODUCED (kernel memory leak)

Finding

ath_hal_v4kEepromAttach() (sys/dev/netif/ath/ath_hal/ah_eeprom_v4k.c:282-399) mallocs HAL_EEPROM_v4k at :311 (ath_hal_malloc β†’ kmalloc(M_ATH_HAL, M_INTWAIT|M_ZERO), ah_osdep.c:118-124; 1152 bytes on this build) and has three post-malloc error returns that neither free the object nor install it:

  • :324 return HAL_EEREAD; β€” mid-image EEPROM read failure (loop at :318-326)
  • :366 return HAL_EEBADSUM; β€” XOR checksum != 0xffff (check at :363)
  • :385 return HAL_EEBADSUM; β€” EEPROM version nibble != AR5416_EEP_VER (14)

The eeprom becomes reachable only at :390-396 (AH_PRIVATE(ah)->ah_eeprom = ee, ah_eepromDetach = v4kEepromDetach), after every error return. The caller (ar9285_attach.c:246-248 ecode != HAL_OK β†’ goto bad β†’ ah->ah_detach(ah) at :393 β†’ ar5416Detach (ar5416/ar5416_attach.c:490-508) β†’ ath_hal_eepromDetach macro (ah_internal.h:474-477)) is a no-op when ah_eepromDetach was never installed β†’ the 1152-byte block is permanently orphaned. This is the third and final member of the eeprom-attach leak family: DF-4873 (ar9287, ~1KB per block), DF-4913 (v14, 4800B, verified live 600 blocks/2.93MB), DF-1995 (ar5210 v1). Both prior verdicts predicted this file has the identical bug β€” it does.

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

KLD PoC v4kleak.c calls the real in-kernel ath_hal_v4kEepromAttach() (ath_hal compiled into the guest kernel; symbol verified via nm /boot/kernel/kernel β†’ ffffffff8034f1a0 T ath_hal_v4kEepromAttach) with a synthetic zeroed struct ath_hal_private whose ah_eepromRead callback serves crafted v4k-variant EEPROM images (image starts at word offset owl_eep_start_loc == 64, per ah_eeprom_v4k.h:34-39):

mode trigger return orphans
EEREAD@324 read callback fails at image word 5 HAL_EEREAD 200/200
EEBADSUM@366 XOR checksum != 0xffff HAL_EEBADSUM 200/200
BADVER@385 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):

  • baseline: 0 blocks β†’ 600 blocks / 675K after trigger; unchanged after kldunload (permanent β€” no back-reference to the orphaned block exists).
  • run 2 (run.2.log): 600 β†’ 1.17K blocks / 1.32M (leak accumulates per attach round; 1152 B Γ— 1200 β‰ˆ 1.32 MB βœ“).
  • control contributed 200 transient allocations that were all freed via the installed v4kEepromDetach (ah_eeprom_v4k.c:267-274) β€” in-use unchanged by control, proving the malloc↔detach pairing is balanced and only the three error paths orphan.

See run.log / run.2.log / console.log in this directory.

Impact ceiling

Unbounded kernel-heap exhaustion: 1152 B per failed attach. Trigger content is attacker-chosen on the untrusted-EEPROM model (corrupt/reprogrammed EEPROM or hostile ar9285 "Kite" card); every attach attempt fails the same way. Repeatable without reboot via hotplug / devd re-probe / driver reload cycles β€” same model as DF-4873/DF-4913 (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 is zeroed at malloc (M_ZERO) and never handed to any consumer after the error return β€” no UAF, no info leak (block never copied out; the diag export path v4kEepromDiag at :131-144 only runs after a successful install), no overflow. Pure memory loss.

Pass-2 negatives worth recording (v4k file beyond the family leak)

  • read loop :318-326 writes exactly sizeof(struct ar5416eeprom_4k) = 376 B (188 words) into the first member of the 1152-B object β€” compile-time bound, not attacker-enlargable. No OOB.
  • v4kEepromReadCTLInfo :232-262: writes ≀ 12Γ—8 entries into ee_rdEdgesPower[96] (max index 91); ctlIndex[12]/ctlData[12] indexed only under i < AR5416_4K_NUM_CTLS. Bounded.
  • v4kEepromGetSpurChan :197-205: ix bound is HALASSERT-only in this function, but both callers loop i < AR5416_EEPROM_MODAL_SPURS (ar5416_attach.c:671-672, ar9280_attach.c:610-611) β€” no attacker-reachable OOB.
  • diag export :131-144 exports device-read + M_ZERO-zeroed data only β€” no uninitialized kernel memory disclosure (M_ZERO at ah_osdep.c:121-124).
  • eepromSwap :147-195: loops bounded by AR5416_4K_MAX_CHAINS=1 / AR5416_EEPROM_MODAL_SPURS=5.

Fix

fix.diff in this directory: ath_hal_free(ee) before each of the three error returns (mirrors the DF-4873/DF-4913 family fix; git apply --check clean against the pristine tree). Fix validation would require rebuilding the guest kernel (ath_hal is compiled in, module symbol shadowing does not apply) β€” not performed: the finding is a pure leak (no corruption) for which fix validation is not mandated, and the PoC control path demonstrates the free-on-error is exactly the installed-detach free.

PoC changes vs the initial seed

Adapted from the DF-4913 v14 harness (same KLD/sysctl pattern, moduledata_t requirement, -I/usr/obj/usr/src/sys/X86_64_GENERIC include). One bug fixed during bring-up: the control-path kprintf dropped its NFAIL argument (-Werror format failure, build.log). v4k-specific deltas: include ah_eeprom_v4k.h (owl_eep_start_loc 64, not 256), NWORDS from struct ar5416eeprom_4k (188), error-return line numbers 324/366/385, control version 0xE001 (AR5416_EEP_VER == 0xE, ah_eeprom_v14.h:44).

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 PoC control path (valid image -> attach succeeds -> v4kEepromDetach frees) demonstrates the free path is balanced, and fix.diff (git apply --check clean) adds the same ath_hal_free(ee) on the three error returns.

fix.diff mirrors the verified control-path lifetime (malloc at :311 <-> v4kEepromDetach free at :272); identical to the DF-4873/DF-4913 family fix.
↓ fix.diffper-fix-DF-4956

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (vmstat -m ath_hal 0 -> 600 blocks/675K, unchanged after kldunload), run.2.log (600 -> 1.17K blocks/1.32M: accumulates), console.log (per-mode '200 attaches, 0 HAL_OK, 200 with ah_eeprom==NULL (orphaned mallocs)' + balanced control + sizeof(HAL_EEPROM_v4k)=1152), build.log, env.txt, fix.diff

PoC changes

Adapted from the DF-4913 v14 harness (KLD + sysctl hw.v4kleak=1 trigger, moduledata_t, -I/usr/obj/usr/src/sys/X86_64_GENERIC). Fixed one bring-up bug: control-path kprintf dropped its NFAIL argument (-Werror=format). v4k deltas: ah_eeprom_v4k.h include (owl_eep_start_loc 64), NWORDS from struct ar5416eeprom_4k (188 words/376B), error lines 324/366/385, control image version 0xE001 (AR5416_EEP_VER 0xE).

Verified recommended fix

ath_hal_free(ee) before each of the three post-malloc error returns (HAL_EEREAD :324, HAL_EEBADSUM :366, HAL_EEBADSUM :385) β€” same fix as DF-4873/DF-4913.

Verdict

Kernel heap memory leak (permanent, 1152 bytes per failed attach) on the v4k EEPROM-attach error paths of ath_hal_v4kEepromAttach: the HAL_EEPROM_v4k malloc'd at ah_eeprom_v4k.c:311 is orphaned by three post-malloc returns β€” HAL_EEREAD at :324 (mid-image read failure), HAL_EEBADSUM at :366 (bad checksum) and :385 (bad version) β€” none of which free it, and AH_PRIVATE(ah)->ah_eeprom / ah_eepromDetach are installed only at :390-396 after every error return. The caller unwind (ar9285_attach.c:246-248 goto bad -> ah_detach -> ar5416Detach:507 -> ath_hal_eepromDetach macro, ah_internal.h:474-477) is a no-op for it. Third and final member of the eeprom-attach leak family (DF-4873 ar9287 ~1KB, DF-4913 v14 4.8KB verified live, DF-1995 ar5210), predicted by both prior verdicts. Reproduced on the guest by calling the real in-kernel function with a synthetic ath_hal whose eepromRead callback serves crafted v4k images (image base owl_eep_start_loc==64): 3 failure modes x 200 attaches orphaned 600 blocks / 675K of malloc type ath_hal (permanent across kldunload), a second trigger round stacked to 1.17K blocks / 1.32M, while 200 valid attach+detach control cycles left in-use unchanged. Attacker model: untrusted EEPROM / hostile ar9285 card (physical access or hotplug) with corrupt checksum/version β€” every attach fails identically, repeatable without bound.