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

Invalid free / non-heap kfree in ath_ioctl_diag when HAL diagcode replaces result pointer (ATH_DIAG_DYN + HAL_DIAG_REVS/FATALERR)

Summary

ath_ioctl_diag allocates outdata via kmalloc(outsize,M_NOWAIT) when ATH_DIAG_DYN set (:201), passes &outdata to ath_hal_getdiagstate (:214). HAL diagcodes HAL_DIAG_REVS (ah.c:886-889 *result=&ah_devid) and HAL_DIAG_FATALERR (ah.c:897-900 *result=&ah_fatalState[0]) REPLACE the callers outdata pointer with interior pointer of ath_hal struct (single kmalloc block). Cleanup at :232-233 kfree(outdata,M_TEMP) then frees this interior/non-heap address -> slab panic (free of unallocated memory) or slab metadata corruption -> potential UAF/escalation. Original kmalloc buffer lost (memory leak). Trigger: ad_id=ATH_DIAG_DYN|HAL_DIAG_REVS(=0x8000|0) or |HAL_DIAG_FATALERR(=0x8000|29). Reachability ungated due to DF-2059. AV:L/PR:L/AC/L, C:L/I:H/A:H. Fix: track driver alloc via separate outbuf ptr; free outbuf not outdata.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2060 Β· 7 files
FileTypeDescriptionSize
VERDICT.md verdict full narrative, mechanism, citations, fix-build result 3.9 KB ↓ raw
README.md readme evidence-pack overview 1.6 KB ↓ raw
fix.diff suggested-fix standalone git-apply-able fix (authored post-verification) 1.0 KB view raw
build.sh repro-script combined-fix nativekernel build command 254 B view raw
run.sh repro-script runtime trigger (HW/module-gated) 408 B view raw
fix_build.log build-log full combined-fix kernel build output (rc=0, -Werror, 0 warnings) 5.6 MB ↓ download
env.txt environment guest uname / kern.version / cc version 289 B view raw
README.md readme evidence-pack overview
↓ download raw

DF-2060 β€” PoC evidence pack

Finding: Invalid free / non-heap kfree in ath_ioctl_diag (ATH_DIAG_DYN + HAL_DIAG_REVS/FATALERR) Class: Free of memory not at start of buffer / invalid free (CWE-590) | Impact ceiling: panic | Confidence: certain

Status

SOURCE-CONFIRMED. The defect is verified real by line-by-line tracing of the audited sys/ tree, but it is HW/module-gated: the audit guest lacks the required hardware/module (Atheros (ath) WLAN NIC present; ATH_DIAGAPI compiled in; reachability ungated only because of DF-2059), so it cannot be triggered at runtime here. See VERDICT.md for the full mechanism and citations.

How to reproduce (on a guest that HAS the hardware)

  1. Ensure the gating precondition is met: Atheros (ath) WLAN NIC present; ATH_DIAGAPI compiled in; reachability ungated only because of DF-2059.
  2. Apply fix.diff to confirm the cited path changes; before the fix the cited code path exhibits the behaviour described in VERDICT.md.
  3. The original finding markdown describes the runtime trigger.

Build validation (Phase 8)

fix.diff applies cleanly to pristine source. All nine findings' fixes were built together; see VERDICT.md "Phase 8" and fix_build.log:

make -j6 nativekernel KERNCONF=X86_64_GENERIC   # rc=0, -Werror, 0 warnings

Files

  • VERDICT.md β€” full narrative, mechanism, citations, fix-build result
  • fix.diff β€” standalone git apply-able fix (authored post-verification)
  • fix_build.log β€” full combined-fix kernel build output (rc=0, -Werror)
  • env.txt β€” guest environment for this verification
  • manifest.json β€” machine-readable catalog
VERDICT.md verdict full narrative, mechanism, citations, fix-build result
↓ download raw

DF-2060 β€” VERDICT

Verdict: REPRODUCED (source-confirmed; HW/module-gated β€” not runtime-triggerable on this guest) Class: Free of memory not at start of buffer / invalid free (CWE-590) Impact ceiling: panic Confidence: certain

Reproduction status

This finding is HW/module-gated and could not be triggered at runtime on the audit guest. The guest has no Atheros NIC, no AdvanSys HBA, no Intel i915 GPU, no AMD Southern-Islands GPU, no LSI MegaRAID controller, and no loaded dm device (only vtnet0). The PoC therefore cannot reach the vulnerable path at runtime here. The bug is instead confirmed by line-by-line source tracing against the audited sys/ tree (master DEV, the same commit the kernel was built from) β€” the defect is real and deterministic given the hardware/module, which is the standard bar for HW-gated driver findings.

Mechanism (source-confirmed)

ath_ioctl_diag() allocates outdata via kmalloc(outsize,M_TEMP) when ATH_DIAG_DYN is set (:201), then passes &outdata to ath_hal_getdiagstate() (:214). For diagcode HAL_DIAG_REVS the HAL REPLACES *result with &AH_PRIVATE(ah)->ah_devid (ah.c:887) and for HAL_DIAG_FATALERR with &ah_fatalState[0] (ah.c:898) -- both interior pointers into the single kmalloc'd ath_hal struct. Cleanup at :232-233 does kfree(outdata,M_TEMP), freeing that interior/non-heap address -> slab panic (free of unallocated memory) or slab-metadata corruption on GENERIC (INVARIANTS). The original kmalloc buffer is also leaked. Trigger: ad_id=ATH_DIAG_DYN|HAL_DIAG_REVS or |HAL_DIAG_FATALERR.

Gating precondition: Atheros (ath) WLAN NIC present; ATH_DIAGAPI compiled in; reachability ungated only because of DF-2059

Exploit chain

Not applicable β€” this finding is HW/module-gated on the audit guest (no triggering hardware/module present), so no userspace-driven escalation chain can be exercised here. Per AGENT.md Phase 6 the valid hard blocker that applies is "vulnerable code path is reachable only on hardware/module absent from this guest." The realistic impact ceiling for the bug itself is panic as documented above (panic for the corruption-class bugs on default GENERIC with INVARIANTS ON; dos for the I/O-wedge/stall bugs; none/graceful-fallback for the validation-bypass and pure missing-check / resource-leak findings).

Fix (authored, git-apply-able)

Track the original kmalloc'd result buffer in a separate outbuf pointer and free outbuf (not the HAL-replaced outdata) on the cleanup path in ath_ioctl_diag() (if_ath_ioctl.c). This fixes both the invalid free and the buffer leak.

The standalone diff is fix.diff in this directory. It applies cleanly to the pristine audited source (git apply --check verified).

Phase 8 β€” combined fix-build validation

All nine findings' fixes were applied together to a single in-guest source tree and built with make -j6 nativekernel KERNCONF=X86_64_GENERIC. The DragonFly kernel compile invokes cc ... -Werror on every translation unit, so this is a true -Werror build.

  • Result: === NK_DONE rc=0 === (full log: fix_build.log)
  • Compiler errors in changed files: 0
  • Compiler warnings (entire tree): 0
  • Patched kernel.stripped sha256: 6086a989213ef7be890180f60edc0fd9827bf60efbc3ed8c969724746e250df6
  • Baseline (unpatched) kernel: DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026

Because the PoC is HW/module-gated, the fix could not be runtime A/B tested on this guest (no way to trigger the bad behaviour on either the baseline or the patched kernel). The fix is therefore classified fix_status = not_testable (compile-validated + source-traced to close the cited path), which is the honest result for HW-gated findings.

Kernel references (verified during this trace)

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

COMPILE-VALIDATED, runtime not_testable. fix.diff applies cleanly and built into the combined nativekernel build: === NK_DONE rc=0 ===, -Werror, 0 warnings in if_ath_ioctl.c. Source-traced closure: the cleanup path now frees the original outbuf allocation, so an interior HAL-supplied pointer is never kfree()'d. Runtime A/B impossible (no ath NIC).

baseline if_ath_ioctl.c:232-233 kfree(outdata) (interior ptr); patched: kfree(outbuf) (original alloc). Combined build === NK_DONE rc=0 ===, 0 warnings. Gating: no ath NIC => runtime not_testable.
↓ fix.diffcombined-fix kernel built rc=0 -Werror (kernel.stripped sha256 6086a989...); not booted β€” runtime not_testable (HW-gated: no Atheros NIC)

Confirmed kernel references

Detail

Exploit chain

Primitive = kfree() of an interior pointer into the ath_hal slab allocation (CWE-590). On default GENERIC (INVARIANTS ON) this is trapped as a slab panic (DoS, the demonstrated ceiling); on INVARIANTS-OFF it is silent slab-metadata corruption with UAF/escalation potential. NO userspace escalation chain exercisable on this guest: BLOCKED by the valid hard blocker that the vulnerable path is reachable only with an Atheros NIC, which is absent here (no ath interface). Concrete next move on real HW: trigger ATH_DIAG_DYN|HAL_DIAG_REVS to corrupt the ath_hal slab bucket, groom the bucket, reclaim the freed interior slot with a controlled object. Chain situation documented in findings/poc/DF-2060/VERDICT.md.

Evidence (decisive lines)

if_ath_ioctl.c:201 outdata=kmalloc(outsize,M_TEMP,M_NOWAIT); :214 ath_hal_getdiagstate(ah,id,indata,insize,&outdata,&outsize); ah.c:887 *result=&AH_PRIVATE(ah)->ah_devid (interior ptr); if_ath_ioctl.c:232-233 kfree(outdata,M_TEMP) frees interior ptr. Combined-fix build: === NK_DONE rc=0 === (-Werror).

PoC changes

findings/poc/DF-2060/ populated: VERDICT.md, fix.diff (track original kmalloc'd buffer in separate outbuf pointer; free outbuf not the HAL-replaced outdata β€” fixes invalid free AND the buffer leak), README.md, build.sh, run.sh, env.txt, fix_build.log, manifest.json.

Verified recommended fix

In ath_ioctl_diag() (if_ath_ioctl.c) keep a separate void *outbuf pointing at the kmalloc'd result buffer; assign outdata=outbuf after allocation; on the cleanup path kfree(outbuf,M_TEMP) instead of outdata. This reclaims the original allocation regardless of the HAL replacing outdata (HAL_DIAG_REVS/FATALERR) and also closes the buffer leak. Standalone diff in findings/poc/DF-2060/fix.diff; supersedes finding proposal (finding suggested the same outbuf approach β€” implemented and compile-validated).

Verdict

SOURCE-CONFIRMED (HW-gated). ath_ioctl_diag() kmalloc's outdata when ATH_DIAG_DYN set (if_ath_ioctl.c:201), passes &outdata to ath_hal_getdiagstate() (:214). For HAL_DIAG_REVS the HAL replaces *result with &AH_PRIVATE(ah)->ah_devid (ah.c:887) and for HAL_DIAG_FATALERR with &ah_fatalState[0] (ah.c:898) β€” interior pointers into the single kmalloc'd ath_hal block. Cleanup kfree(outdata,M_TEMP) at if_ath_ioctl.c:232-233 frees that interior/non-heap address -> slab panic (free of unallocated memory) or slab-metadata corruption on default GENERIC (INVARIANTS ON); the original kmalloc buffer is also leaked. Trigger: ad_id=ATH_DIAG_DYN|HAL_DIAG_REVS (or |HAL_DIAG_FATALERR). Reachability ungated only via DF-2059.