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)
PoC verification
Evidence pack
findings/poc/DF-2060 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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)
- Ensure the gating precondition is met: Atheros (ath) WLAN NIC present; ATH_DIAGAPI compiled in; reachability ungated only because of DF-2059.
- Apply
fix.diffto confirm the cited path changes; before the fix the cited code path exhibits the behaviour described inVERDICT.md. - 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 resultfix.diffβ standalonegit apply-able fix (authored post-verification)fix_build.logβ full combined-fix kernel build output (rc=0, -Werror)env.txtβ guest environment for this verificationmanifest.jsonβ machine-readable catalog
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.strippedsha256: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_testableCOMPILE-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.
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.
No comments yet.