# DF-4320 — VERDICT

**Status: inconclusive at kernel level (feature absent on the audit guest);
the missing privilege gate itself is certain from source, fully traced.**

## What the finding claims

`SIOCGATHDIAG` (the ath(4) HAL diagnostic ioctl) is dispatched to
`ath_ioctl_diag()` **without any privilege check** on DragonFly:

```
sys/dev/netif/ath/ath/if_ath_ioctl.c:285-297   SIOCZATHSTATS *does* check
                                              caps_priv_check_self(SYSCAP_NODRIVER)   :288
sys/dev/netif/ath/ath/if_ath_ioctl.c:299-300   case SIOCGATHDIAG:
                                                return (ath_ioctl_diag(sc, data));  <-- NO check
sys/dev/netif/ath/ath/if_ath_ioctl.c:301-302   case SIOCGATHPHYERR: same             <-- NO check
```

The full unprivileged dispatch chain (traced, no gate anywhere):

1. `ioctl(2)` on a datagram socket → `sys/net/if.c:2409-2415` default →
   `so_pru_control_direct` (unknown driver ioctl passed through);
2. `sys/netproto/802_11/wlan/ieee80211_ioctl.c:3516-3520` default —
   "Pass unknown ioctls first to the driver" → `ic->ic_ioctl` = `ath_ioctl`;
3. `ath_ioctl` case SIOCGATHDIAG → `ath_ioctl_diag` (`if_ath_ioctl.c:170-235`)
   — copyin of attacker `ad_in_size`/`ad_out_size` buffers, then
   `ath_hal_getdiagstate(ah, id, indata, insize, &outdata, &outsize)`;
4. `sys/dev/netif/ath/ath_hal/ah.c:878-935` — unconditional handlers:
   * `HAL_DIAG_SETREGS` (33): `ath_hal_setregs()` — **arbitrary device
     MMIO writes** with attacker-supplied `addr,value` pairs, no
     validation at all (ah.c:888-891);
   * `HAL_DIAG_REGS` (13): `ath_hal_getregdump()` — **arbitrary register
     range dump** returned to userland; the keycache registers
     (`AR_KEYTABLE_KEYn`, 0x9800+, `ar5212reg.h`) hold installed WEP/TKIP
     key material in plaintext → 802.11 key disclosure to any local user;
   * `HAL_DIAG_EEREAD` (17): arbitrary EEPROM word read.
   Plus NULL-deref panics with the ATH_DIAG_IN/ATH_DIAG_DYN flag bits
   unset: `getregdump(ah, NULL, NULL, attacker_outsize)` /
   `ath_hal_setregs(ah, NULL, argsize)` dereference NULL
   (ah.c:848-856/865-870 with `indata == NULL`) — trivial unprivileged
   panic on a vulnerable config. The ar5212-specific handlers behind
   `ar5212GetDiagState` (ar5212_misc.c:1049-1126) add
   `HAL_DIAG_ANI_CMD` → `ar5212AniControl` with unchecked `cmd`, which
   NULL-derefs `params->maxNoiseImmunityLevel` when no ANI state exists
   (ar5212_ani.c:230-243).

FreeBSD upstream gates the identical cases with
`priv_check(curthread, PRIV_DRIVER)`; DragonFly's port kept the check for
SIOCZATHSTATS only (the `caps_priv_check_self` at :288) and dropped it
for the two DIAGAPI ioctls.

## Why it is not kernel-reproducible on THIS guest (and what was verified instead)

* The guest kernel is `X86_64_GENERIC`
  (`DragonFly 6.5-DEVELOPMENT #0 ... /usr/obj/usr/src/sys/X86_64_GENERIC`),
  which does **not** include `options ATH_DIAGAPI`
  (`sys/config/X86_64_GENERIC` — no ATH_DIAGAPI; only LINT64 lists it at
  :1121). The whole `#ifdef ATH_DIAGAPI` block — including
  `ath_ioctl_diag` — is not compiled: `nm /boot/kernel/if_ath.ko |
  grep ath_ioctl_diag` → no symbol (see static_check.log).
* The guest has no Atheros NIC (virtio-only PCI bus, `pciconf -l` in
  env.txt), so even a recompiled kernel could not be driven into the HAL
  path without hardware.

What WAS verified on the guest:

* `poc` (unprivileged, uid=1001) issued real `SIOCGATHDIAG` ioctls with a
  `HAL_DIAG_SETREGS` payload and a `HAL_DIAG_REGS` keycache-dump payload
  on every interface: non-wlan interfaces answer EINVAL (ether layer),
  i.e. no privilege gate was hit — and none exists in the source path
  (run.log).
* Static evidence that the HAL side handlers are compiled unconditionally
  into `ath_hal.ko` (disassembly of `ath_hal_getdiagstate` at +0x67b0
  shows comparisons against diag codes 13 (REGS), 17 (EEREAD), 29
  (FATALERR), 30 (11NCOMPAT), 33 (SETREGS)) — the only missing piece on a
  stock system is the ioctl case in if_ath.ko, which any
  `options ATH_DIAGAPI` kernel build turns on.

## Exploit chain (on a kernel built with `options ATH_DIAGAPI` + ath NIC)

```
unprivileged local user
  → socket(AF_INET,SOCK_DGRAM) + ioctl(SIOCGATHDIAG, {ad_id=HAL_DIAG_REGS|IN|DYN,
      ad_in_data=[{0x9800,0x983f},...], ad_out_size=4096})
  → copyout of keycache registers → recover WEP/TKIP group/pairwise keys
  → (or) ad_id=HAL_DIAG_SETREGS, write arbitrary device registers
  → (or) ad_id=HAL_DIAG_ANI_CMD, NULL-deref panic
  → (or) HAL_DIAG_REGS with crafted sizes → DF-4321 heap overflow → code exec
```

## Bottom line

Certain source-level vulnerability (CWE-862), unconditionally missing gate
whose sibling ioctl demonstrates the intended pattern in the same switch.
Exploitation requires a kernel built with `options ATH_DIAGAPI` (not in
default GENERIC) and an ath(4) device — therefore Medium severity per the
project rubric ("requires unusual config"), with High impact on such
configurations. Not reproducible on the audit guest (feature absent);
fix.diff adds the gate mirroring :288.

## 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.
