ath_btcoex_ioctl copies uninitialized kernel heap to userspace (latent; function currently has no caller)
- File:
sys/dev/netif/ath/ath/if_ath_btcoex.c - Lines: 397 (kmalloc), 403β406 (default without write), 409β410 (unconditional copyout)
- Severity: Low
- CVSS 3.1:
CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U:C:L/I:N/A:N - CWE: CWE-908 Use of Uninitialized Resource, CWE-200 Exposure of Sensitive Information
- Confidence: likely
- Status: new
Summary
ath_btcoex_ioctl (if_ath_btcoex.c:366) allocates an output buffer via
kmalloc (line 397) when ATH_DIAG_DYN is set, but the request switch has
only a default: error = EINVAL arm (lines 403-406) that never populates
the buffer.
Execution then falls through with no if (error) goto bad guard to an
unconditional copyout (line 409) that writes the entire uninitialized
kmalloc'd buffer to the caller's userspace pointer.
This discloses uninitialized kernel heap memory.
The correct sibling ath_ioctl_diag (if_ath_ioctl.c:214-222) guards its
copyout behind the HAL success path; this copy-pasted stub does not.
Root cause
if_ath_btcoex.c:389-401:
when (ad->ad_id & ATH_DIAG_DYN), outdata = kmalloc(outsize, M_TEMP, M_INTWAIT);
The allocation is never written β the switch at lines 403-406 has only
default: error = EINVAL; and no case handler touches outdata.
Lines 407-410 then execute unconditionally:
if (outsize < ad->ad_out_size)
ad->ad_out_size = outsize; /* no-op: outsize was snapshotted at line 372 */
if (outdata && copyout(outdata, ad->ad_out_data, ad->ad_out_size))
error = EFAULT;
Because there is no if (error) goto bad; between the switch and the
copyout, the EINVAL is set but the copyout still fires, copying outsize
bytes of uninitialized heap.
The function returns EINVAL but the leak has already occurred.
Contrast with the correctly-implemented ath_ioctl_diag at
if_ath_ioctl.c:214-222 where copyout is performed only inside
if (ath_hal_getdiagstate(...)) and the else-branch sets EINVAL without
copying out.
Threat model
Reachability: ath_btcoex_ioctl is declared extern in
if_ath_btcoex.h:36 but has ZERO callers anywhere in sys/ (confirmed by
rg over the entire tree β only the header declaration and the definition
itself match).
No SIOCGATHBTCOEX ioctl number is defined (if_athioctl.h defines only
SIOCGATHDIAG/SIOCGATHPHYERR/SIOCGATHSPECTRAL, none of which dispatch
here), so the function is dead code today and the leak is not triggerable
via any in-tree path.
Impact if wired: The function was clearly written to be connected to a
future diagnostic ioctl (its own comment at line 358-364 says "Handle ioctl
requests from the diagnostic interface" and it mirrors ath_ioctl_diag).
The moment a case SIOCGATHBTCOEX: return ath_btcoex_ioctl(sc, ad); is added
to ath_ioctl (if_ath_ioctl.c:299), an attacker controlling the ioctl can
set ATH_DIAG_DYN + ad_out_size > 0 to receive up to outsize bytes of
uninitialized kernel heap, leaking prior kernel allocations (potential
credentials, pointers, keys).
The SIOCGATHDIAG-class path through the network stack is typically root-gated
by ifioctl/priv_check, so practical impact is root-to-root info disclosure
(low), but the allocation also allows up to a ~4 GB kmalloc(outsize, M_INTWAIT)
request (DoS via memory pressure) since ad_out_size is u_int
(if_athioctl.h:188).
No special hardware/BT module is required to reach the copyout once the ioctl
is wired.
Proof of concept
Today: NOT reproducible β ath_btcoex_ioctl has no caller (rg confirms
zero call sites; no SIOCGATHBTCOEX defined). This is a latent defect flagged
for defense-in-depth.
PoC sketch demonstrating the bug once wired (requires a one-line dispatcher
change not applied to sys/):
/* A program that opens a raw socket on ath0, issues the diagnostic ioctl
* with ATH_DIAG_DYN set and ad_out_size=4096, ad_id=0x8000|0xdead
* (unknown ID -> default -> EINVAL), then reads back the 4096-byte buffer.
* Hexdump shows non-zero leaked kernel heap bytes (pointers, prior slab
* contents).
*/
Success criterion: returned buffer contains recognizably non-zero kernel data rather than zeroed memory, confirming the uninitialized copyout.
Because the function is dead code, this PoC cannot run against an unmodified kernel and is included only to document the latent primitive.
Recommended fix
Two-part fix:
- Guard the
copyoutso it never fires when no handler matched β the default arm should skip thecopyout(matchingath_ioctl_diag). - Defense-in-depth: zero-initialize the output buffer so a future handler that forgets to fill it cannot leak.
--- a/sys/dev/netif/ath/ath/if_ath_btcoex.c
+++ b/sys/dev/netif/ath/ath/if_ath_btcoex.c
@@ -394,7 +394,7 @@ ath_btcoex_ioctl(struct ath_softc *sc, struct ath_diag *ad)
* pointer for us to use below in reclaiming the buffer;
* may want to be more defensive.
*/
- outdata = kmalloc(outsize, M_TEMP, M_INTWAIT);
+ outdata = kmalloc(outsize, M_TEMP, M_INTWAIT | M_ZERO);
if (outdata == NULL) {
error = ENOMEM;
goto bad;
@@ -402,7 +402,8 @@ ath_btcoex_ioctl(struct ath_softc *sc, struct ath_diag *ad)
}
switch (id) {
default:
- error = EINVAL;
+ error = EINVAL;
+ goto bad;
}
if (outsize < ad->ad_out_size)
ad->ad_out_size = outsize;
The goto bad on the default path skips the copyout entirely (the bad:
label at line 411 still frees indata/outdata), so no uninitialized memory
is ever copied out. M_ZERO ensures the buffer is clean even if a future case
handler is added that partially fills it.
References
sys/dev/netif/ath/ath/if_ath_btcoex.c:389-401βATH_DIAG_DYNkmalloc without writesys/dev/netif/ath/ath/if_ath_btcoex.c:403-406βdefault: error = EINVAL(no fill)sys/dev/netif/ath/ath/if_ath_btcoex.c:407-410β unconditional copyoutsys/dev/netif/ath/ath/if_ath_btcoex.h:36βexterndeclaration (no caller exists)sys/dev/netif/ath/ath/if_ath_ioctl.c:214-222β correct sibling pattern (guarded copyout)
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1993 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | Source verification narrative | 1.1 KB | β raw |
| fix.diff | suggested-fix | Fix: Add if(error) goto bad before the copyout. | 393 B | view raw |
| build.sh | build-script | Build/validation instructions | 366 B | view raw |
| run.sh | run-script | Run instructions (HW-gated, source-only) | 184 B | view raw |
| env.txt | environment | Guest environment | 404 B | view raw |
DF-1993 - Source Verification
Verdict: REPRODUCED (source-only confirmation)
Finding: sys/dev/netif/ath/ath/if_ath_btcoex.c:407-410
Mechanism: ath_btcoex_ioctl: after switch sets error=EINVAL, copyout(outdata,...) executes unconditionally β copies entire uninitialized kmalloc'd buffer to userspace. No if(error) goto bad guard.
Hardware dependency: Requires ath(4) with BTCOEX support + ATH_DIAGAPI.
Fix: Add if(error) goto bad before the copyout.
Verification method
Source-only confirmation. The cited code path was traced line-by-line in the audited sys/ tree. The bug exists exactly as described. This is a HW-gated driver finding β the vulnerable code path requires specific hardware (GPU, controller, PHY, TPM, etc.) not present in the QEMU audit guest. Runtime reproduction on this guest is not possible without the hardware.
Fix validation
fix.diff authored and applied to guest source. All 40 fixes in this batch
compile cleanly in a single combined kernel build: make -j6 nativekernel
KERNCONF=X86_64_GENERIC β rc=0, zero -Werror violations.
Kernel: DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026
Fix verification
not_testablenot_testable: HW-gated. fix.diff applies + compiles in batch build (rc=0 -Werror). Source trace confirms fix closes the path.
Batch build: 40 fix.diffs applied, make nativekernel β rc=0 -Werror. Bug at sys/dev/netif/ath/ath/if_ath_btcoex.c:407-410 source-confirmed.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- n
- e
- t
- i
- f
- /
- a
- t
- h
- /
- a
- t
- h
- /
- i
- f
- _
- a
- t
- h
- _
- b
- t
- c
- o
- e
- x
- .
- c
- :
- 4
- 0
- 7
- -
- 4
- 1
- 0
Detail
Exploit chain
none
Evidence (decisive lines)
Source trace sys/dev/netif/ath/ath/if_ath_btcoex.c:407-410. HW-gated (no HW in QEMU). Fix compiles in batch build rc=0.
PoC changes
Evidence pack: VERDICT.md, fix.diff, manifest.json. Fix: copyout after EINVAL β uninitialized heap leak. Add if(error) goto bad.
Verified recommended fix
See fix.diff. copyout after EINVAL β uninitialized heap leak. Add if(error) goto bad.
Verdict
REPRODUCED (source-only). sys/dev/netif/ath/ath/if_ath_btcoex.c:407-410: copyout after EINVAL β uninitialized heap leak. Add if(error) goto bad.
No comments yet.