# 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`:

```c
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 == NULL` until the
  first `ar5416AniReset()` (only there is `ah_curani` published,
  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_CMD` forwards user (cmd, param) verbatim to
  `ah_aniControl()` (ar5212_misc.c:1098-1103, reached from ar5416 via
  ar5416GetDiagState → ar5212GetDiagState, ar5416_misc.c:557), so
  `ioctl(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_DETECTION`
  gives 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)

```sh
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.
