# DF-4893 VERDICT — ah_osdep.c ALQ TOCTOU/UAF: latent, unreachable on stock DF — `untested` (no runnable path)

## Bottom line

The AH_DEBUG_ALQ block of `sys/dev/netif/ath/ath_hal/ah_osdep.c` contains a
textbook TOCTOU on the module-global `struct alq *ath_hal_alq`: unlocked
consumers (`if (ath_hal_alq)` at sys/dev/netif/ath/ath_hal/ah_osdep.c:297,
:336, :353 → `alq_get(ath_hal_alq, …)` at :264/:276) racing the sysctl
disable path `alq_close(ath_hal_alq); ath_hal_alq = NULL;`
(ah_osdep.c:230-232) yield a use-after-free **write** (`alq_post` through a
freed `struct alq`). The code shape is certain (CWE-367, FreeBSD-inherited).

It is **triple-dead on DragonFly**:

1. `AH_DEBUG_ALQ` cannot be selected — its `sys/conf/options` mapping is
   commented out (sys/conf/options:551); config(8) rejects the option name.
2. The TU cannot compile even if force-defined — `#include <sys/alq.h>` is
   commented out (ah_osdep.c:202) and **no alq(9) implementation or header
   exists anywhere in the tree** (verified `find sys -name "*alq*"`; the
   other would-be consumers, ieee80211_alq.c / if_ath_alq.c, are themselves
   excluded from the build or guard alq calls behind the same dead option —
   if_ath_alq.c:51-53 has its alq include `#ifdef`'d empty for DF).
3. Second compile error inside the block — ALQ-branch `ath_hal_reg_read`
   calls `ath_hal_reg_whilst_asleep()` at ah_osdep.c:324, defined only under
   `#ifdef AH_DEBUG` (:153-171).

Guest evidence (DragonFly 6.5-DEVELOPMENT x86_64, ath compiled into GENERIC
— `hw.ath.*` nodes live, `ath_hal.ko` not loaded, no Atheros PCI device):

* `sysctl hw.ath.hal.alq` / `.debug` / `.alq_logfile` → **unknown oid**
  (run.log) — the block is absent from the running kernel, as predicted.
* `pciconf -l | grep -ciE "ath|1002"` → 0 — no Atheros or AMD GPU hardware
  in the guest, so no runtime register traffic can be produced either.

Even on FreeBSD where this code builds, exploitation requires **root** to
toggle `hw.ath.hal.alq` precisely while another CPU is between the NULL
check and `alq_get` — a privileged-user-triggered race, not an unpriv path.

## Verification honesty

* No escalation / panic was attempted on the guest because there is no
  compiled target and no hardware: every avenue ends at "code does not
  exist in the running kernel" (runtime-proven, not assumed).
* `trigger.c` documents the would-be reproducer shape (root sysctl toggler
  thread vs. register-traffic thread) and `run.sh` probes each precondition;
  on this guest all probes report ABSENT (exit 3).
* Classification: status `untested`, impact `none` on stock DF; filed as
  Info so the race is on record before anyone imports alq(9) and flips the
  option on wholesale.

## Baseline vs fix validation

Not applicable — nothing reproduced, so there is no bad behavior to
validate a fix against. fix.diff is a source-level hardening patch (delete
the dead block) authored from the line-accurate analysis above; per audit
rules it is NOT applied to sys/.

## Production-active surface of ah_osdep.c (audited clean)

`ath_hal_malloc` M_ZERO|M_INTWAIT (ah_osdep.c:120-124 — zeroed, cannot
fail, no uninit leak to HAL softcs) · `ath_hal_vprintf`→`kvprintf` with
HAL-internal fmt (:132-136) · `ether_sprintf` DF per-CPU buffer (safe)
· non-ALQ `ath_hal_reg_write`/`reg_read` (:378-424): optional serialise
lock taken/released around the bus op + correct barrier placement
(barrier-before-read, barrier-after-write) · SYSCTL_STRING logfile node:
`sizeof(kernelname)` == MAXPATHLEN == `sizeof(ath_hal_logfile)`
(sys/kern/kern_mib.c:178) → no write overflow · module glue serialized
(:440-461).

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