# DF-5157 — ath LED blink callout survives ath_detach() teardown (re-armed by a draining tx task) — evidence pack

## Status: UNTESTED on the audit guest (no Atheros NIC exists in QEMU; there is
## no ath hardware emulation). The finding rests on a verified line-by-line
## code trace of the teardown ordering. See VERDICT.md.

## What the finding is

`ath_led_event()` (if_ath_led.c:188-195) is called from tx-completion
taskqueue workers (if_ath.c:4570, 4628, 4676) with no `sc_running` gate and no
lock held (the LED call sits between the ATH_LOCK sections at if_ath.c:4554
and 4573). It re-arms the LED callout chain
(`ath_led_blink` -> `ath_led_off` -> `ath_led_done`, if_ath_led.c:179-186)
which keeps `sc_ledtimer` continuously armed for the whole blink cycle
(ledon/ledoff up to 500ms/130ms, if_ath.c:6213-6232).

`ath_detach()` (if_ath.c:1385-1436) does:

1. `ath_stop(sc)` — cancels `sc_ledtimer` **once** (if_ath.c:2703) — then
   disables interrupts (2713);
2. `taskqueue_free(sc->sc_tq)` (if_ath.c:1416) — which *runs* every still
   pending task (`taskqueue_run(queue, 1)`, kern/subr_taskqueue.c:151-157).
   A tx-completion task that was enqueued before interrupts were disabled now
   executes and calls `ath_led_event()` -> `ath_led_blink()` ->
   `callout_reset(&sc->sc_ledtimer, ...)` — **after the cancel**, re-arming
   the timer;
3. `ath_hal_detach()` (if_ath.c:1435) frees the HAL; the softc is freed by
   newbus when the device is destroyed after detach returns.

Nothing cancels/drains `sc_ledtimer` after step 2. The re-armed callout fires
`ath_led_off()` (if_ath_led.c:166-173) hundreds of milliseconds later — well
after detach completed — dereferencing the freed softc and the freed HAL
(`sc->sc_ah`), then re-arms `ath_led_done()` which writes `sc->sc_blinking` on
freed memory: a use-after-free (read + call through freed HAL, write to freed
softc).

Preconditions: Atheros NIC present; `sc_softled` active (auto-on for
AR5212_DEVID_IBM/AR5211_DEVID, if_ath.c:908, or enabled at runtime by root via
`dev.ath.X.softled`, if_ath_sysctl.c:189-206); a tx completion pending/in
flight at teardown (interface under load during `kldunload if_ath` /
cardbus-PCI hot removal).

## Why untested (and what would be needed)

The audit guest is QEMU 7.2 with virtio/e1000-class devices only (see
env.txt: `pciconf -l`); QEMU has no Atheros WLAN emulation, so `ath0` cannot
exist. A reproduction would need real IBM-devid ath hardware (or any ath NIC +
`sysctl dev.ath.X.softled=1`) under tx load, then unplug/unload during a
blink — hardware-lab work outside this environment.

## Fix

`fix.diff` — re-cancel `sc_ledtimer` in `ath_detach()` immediately after
`taskqueue_free()`, when no task can re-arm it again (DragonFly's
`callout_cancel` is synchronous — kern_timeout.c:1032-1034 — and CANCEL takes
precedence over a handler-side RESET at INPROG-clear, kern_timeout.c:218-229,
so one cancel after the task drain fully quenches the chain).
