ar5416AniControl NULL-pointer dereference when driven before the first channel reset (ah_curani == NULL): kernel panic via HAL_DIAG_ANI_CMD
Summary
Only PRESENT/MODE guard against a missing current channel; every level/threshold command dereferences params (NULL until the first AniReset) or writes through aniState. Attach enables all commands (ah_ani_function=0xffffffff) and the softc is zeroed until first tune, while the diag interface forwards user (cmd,param) verbatim. Unpriv local panic on ATH_DIAGAPI kernels (which lack the SIOCGATHDIAG priv check per DF-2059/4320); root-only on stock. Harness-verified decision flow with the exact NULL load replicated. aniState-NULL guard fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4526 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| ani_null_deref.c | β | 6.0 KB | view raw | |
| build.sh | β | 102 B | view raw | |
| run.sh | β | 151 B | view raw | |
| run.log | β | 1.3 KB | view raw | |
| fix.diff | β | 880 B | view raw | |
| README.md | β | 3.0 KB | β raw | |
| VERDICT.md | β | 3.3 KB | β raw | |
| verdict.json | β | 3.1 KB | view raw | |
| env.txt | β | 1.7 KB | view raw |
PoC DF-4526 β ar5416AniControl NULL-pointer deref before first channel set
What this is
ar5416AniControl() handles being called with no current channel
("This function may be called before there's a current channel (eg to
disable ANI.)", ar5416_ani.c:180-185) only for HAL_ANI_PRESENT and
HAL_ANI_MODE. For every other command it dereferences
params/aniState:
struct ar5212AniState *aniState = ahp->ah_curani; /* :177 */
const struct ar5212AniParams *params = AH_NULL; /* :178 */
if (aniState != AH_NULL)
params = aniState->params; /* :184-185 */
...
if (((1 << cmd) & AH5416(ah)->ah_ani_function) == 0) /* :217 gate */
return AH_FALSE;
switch (cmd) {
case HAL_ANI_NOISE_IMMUNITY_LEVEL:
u_int level = param;
if (level > params->maxNoiseImmunityLevel) { /* :230 NULL READ */
...
case HAL_ANI_OFDM_WEAK_SIGNAL_DETECTION:
...
aniState->ofdmWeakSigDetectOff = !on; /* :296 NULL WRITE */
Attach-time state makes both reachable through the diagnostic api:
- the ar5416 softc is allocated zeroed (ar5416_attach.c:312
"NB: memory is returned zero'd") β
ah_curani == NULLuntil the firstar5416AniReset()(only there isah_curanipublished, ar5416_ani.c:561); ar5416Attach()enables every ANI command:AH5416(ah)->ah_ani_function = 0xffffffff(ar5416_attach.c:244) β the function gate at :217 passes;HAL_DIAG_ANI_CMDforwards user (cmd, param) verbatim toah_aniControl()(ar5212_misc.c:1098-1103, reached from ar5416 via ar5416GetDiagState β ar5212GetDiagState, ar5416_misc.c:557), soioctl(SIOCGATHDIAG, {HAL_DIAG_ANI_CMD, {1, 0}})on an ath(4) device that has not yet tuned a channel executes the NULL deref β kernel page fault β panic.HAL_ANI_OFDM_WEAK_SIGNAL_DETECTIONgives a NULL write at offset 3 instead.
Reachability gates: requires a kernel built with options ATH_DIAGAPI
(not in stock X86_64_GENERIC β LINT64 only), ath(4) hardware present,
and the SIOCGATHDIAG ioctl β which has no privilege check on
DragonFly (if_ath_ioctl.c:299-300; contrast SIOCZATHSTATS at :288;
already filed as DF-2059/DF-4320), making the panic reachable by any
local user on such kernels.
Why a userspace harness (hardware gate)
The audit QEMU guest has no Atheros radio and QEMU cannot emulate one,
so the in-kernel path cannot be executed here (same gate as DF-1676/
DF-2511 evidence packs). ani_null_deref.c is a compile-faithful
replica of the dispatch flow with the attach-time state; --deref
actually performs the NULL load and dies with SIGSEGV, demonstrating
the fault is on the cited instruction.
Build / run (guest or any POSIX host)
cc -O2 -Wall -o ani_null_deref ani_null_deref.c
./ani_null_deref # flow trace, flags the deref -> BUGGY
./ani_null_deref --deref # performs the NULL read -> SIGSEGV
./ani_null_deref --fixed # simulates the guard -> PATCHED
Fix
fix.diff β NULL guard after the PRESENT/MODE handling, before the function gate.
VERDICT β DF-4526
Status: not_reproduced on the guest (hardware-gated: no ath(4) radio exists in QEMU). Impact if the gate is met: kernel NULL-deref panic (local DoS). The defect is code-certain; the userspace harness proves the faulting instruction executes under attach-time state.
The bug
ar5416AniControl() (sys/dev/netif/ath/ath_hal/ar5416/ar5416_ani.c:172)
guards only HAL_ANI_PRESENT (:190) and HAL_ANI_MODE (:193) against a
missing current channel. All level/threshold commands dereference
params (NULL whenever ah_curani == NULL, :178-185) or aniState
itself:
- :230
params->maxNoiseImmunityLevel(HAL_ANI_NOISE_IMMUNITY_LEVEL) - :317
params->maxFirstepLevel(HAL_ANI_FIRSTEP_LEVEL) - :336
params->maxSpurImmunityLevel(HAL_ANI_SPUR_IMMUNITY_LEVEL) - :296
aniState->ofdmWeakSigDetectOff = !on(NULL WRITE) - :310
aniState->cckWeakSigThreshold = high(NULL WRITE)
Why it is reachable
- ar5416 softc is zeroed at allocation (ar5416_attach.c:312) β
ah_curani == NULLafter attach; the pointer is only published inar5416AniReset()(ar5416_ani.c:561), which runs on the first channel set. ar5416Attach()setsah_ani_function = 0xffffffff(ar5416_attach.c:244) β the command gate at ar5416_ani.c:217 passes for every command.HAL_DIAG_ANI_CMD(ar5212_misc.c:1098-1103) passes user (cmd, param) straight toAH5212(ah)->ah_aniControl(); ar5416 routes its diag interface through the same handler (ar5416_misc.c:530,557). The SIOCGATHDIAG entry has no privilege check (if_ath_ioctl.c:299-300 β DF-2059/DF-4320), so onoptions ATH_DIAGAPIkernels any local user with a socket can trigger the panic between driver attach and first channel tune (e.g. immediately after boot, before ifconfig sets a channel).
Result in-kernel: read of *(int *)0x4-ish or write to *(uint8_t *)3
β page fault β Fatal trap 12 panic. Panic-only; no corruption
primitive (fixed low address, fixed size).
Verification performed
- Full code-path trace (cited lines) from ioctl entry to faulting instruction β no intervening guard exists.
ani_null_derefharness (this pack): replicates the dispatch withah_curani=NULL,ah_ani_function=0xffffffff;--derefmode executes the NULL load and receives SIGSEGV on exactly the replicated instruction;--fixedshows the proposed guard returns AH_FALSE. Ran on the audit guest (BUILD_OK; outputs in run.log).- Hardware gate:
pciconf -lon the audit guest shows no Atheros PCI device; QEMU 7.2 has no ath HAL emulation; the ath driver cannot attach β in-kernel execution impossible in this environment.
Fix
One-line guard (fix.diff):
+ if (aniState == AH_NULL) {
+ HALDEBUG(ah, HAL_DEBUG_ANI, "%s: no current ani state\n", __func__);
+ return AH_FALSE;
+ }
placed after the HAL_ANI_PRESENT/HAL_ANI_MODE handling, before the function gate. (Upstream ar5212_ani.c has the same shape and would take the same guard.)
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_testableHardware gate (no Atheros NIC emulatable in QEMU): in-kernel reproduction impossible in this environment; harness demonstrates the faulting instruction executes under attach-time state and that the one-line guard in fix.diff diverts the flow to AH_FALSE.
run.log (--fixed mode), fix.diff
Confirmed kernel references
- sys/dev/netif/ath/ath_hal/ar5416/ar5416_ani.c:177-185
- sys/dev/netif/ath/ath_hal/ar5416/ar5416_ani.c:217
- sys/dev/netif/ath/ath_hal/ar5416/ar5416_ani.c:230
- sys/dev/netif/ath/ath_hal/ar5416/ar5416_ani.c:296
- sys/dev/netif/ath/ath_hal/ar5416/ar5416_attach.c:244
- sys/dev/netif/ath/ath_hal/ar5212/ar5212_misc.c:1098-1103
- sys/dev/netif/ath/ath/if_ath_ioctl.c:299-302
Detail
Evidence (decisive lines)
ani_null_deref.c, run.log (BUILD_OK + BUGGY/BUGGY-SIGSEGV/PATCHED outputs), VERDICT.md (path:line trace incl. attach-time state), fix.diff
PoC changes
No seed PoC. Wrote the decision-flow harness replicating the dispatch with attach-time state (zeroed softc + ah_ani_function=0xffffffff).
Verified recommended fix
Return AH_FALSE when aniState==AH_NULL after the PRESENT/MODE handling in ar5416AniControl().
Verdict
Code-certain NULL-pointer dereference in ar5416AniControl(), hardware-gated on this guest (no ath(4) radio; QEMU cannot emulate the Atheros HAL). ar5416AniControl() guards only HAL_ANI_PRESENT/HAL_ANI_MODE against a missing current channel; every level/threshold command dereferences params (NULL whenever ah_curani==NULL, ar5416_ani.c:178-185) at :230/:317/:336 or writes through aniState at :296/:310. The ar5416 softc is allocated zeroed (ar5416_ani.c 'ah_curani' is only published in ar5416AniReset, :561) and ar5416Attach() enables all commands (ah_ani_function=0xffffffff, ar5416_attach.c:244), so the function gate at :217 passes; HAL_DIAG_ANI_CMD (ar5212_misc.c:1098-1103, reachable from ar5416 via ar5416_misc.c:557) forwards user (cmd,param) verbatim -> immediate page fault -> kernel panic. On kernels built with options ATH_DIAGAPI (not stock GENERIC) the SIOCGATHDIAG entry has no privilege check (if_ath_ioctl.c:299-300; DF-2059/DF-4320), making it a local unprivileged panic there. Verified by full path trace + a compile-faithful userspace harness whose --deref mode executes the NULL load and takes SIGSEGV exactly on the replicated instruction (see run.log).
No comments yet.