# DF-3618 — ath_vap_delete: racy second TX drain around ieee80211_vap_detach → freed-vap use

## The defect (author-acknowledged in-tree)
`sys/dev/netif/ath/ath/if_ath.c:1766-1884` (ath_vap_delete):

```c
	ieee80211_vap_detach(vap);            /* 1793 */

	/*
	 * XXX Danger Will Robinson! Danger!
	 * Because ieee80211_vap_detach() can queue a frame (the station
	 * diassociate message?) after we've drained the TXQ and flushed
	 * the software TXQ, we will end up with a frame queued to a node
	 * whose vap is about to be freed.
	 * ... This may be racy - the ath task may be running and the
	 * packet may be being scheduled between sw->hw txq. Tsk.
	 */
	ath_draintxq(sc, ATH_RESET_DEFAULT);  /* 1812 */
	...
	kfree(avp, M_80211_VAP);              /* 1860 */
```

Sequence that leaves a use-after-free window:

1. 1785: interrupts disabled (`ath_hal_intrset(ah, 0)`) — but taskqueue
   tasks already enqueued/running are NOT stopped: nothing here calls
   taskqueue_block()/taskqueue_drain() (contrast ath_reset:2889/3021 and
   ath_key_update_begin:3542 which do block the tq).
2. 1793: `ieee80211_vap_detach()` — per the in-tree comment this queues a
   frame (disassoc) onto TX machinery *after* the first drain.
3. That frame is scheduled sw→hw by the still-live `sc_txtask`/`sc_txqtask`
   worker (enqueued by earlier interrupts, or by ath_tx_kick from another
   thread) concurrently with the second `ath_draintxq` at 1812 — the exact
   interleave the comment itself calls racy.
4. 1835: `ath_tx_draintxq(sc, &avp->av_mcastq)` drains the vap's software
   mcast queue — a field INSIDE avp — without any synchronization against
   the task worker that can, in the same window, be manipulating
   av_mcastq-queued buffers (`ath_tx_processq` → completion callbacks that
   call back into vap methods).
5. 1860: `kfree(avp)` — any buffer whose completion/callback still
   references the vap (bf_comp handlers, mcastq linkage, ni->ni_vap) now
   touches freed memory.

Concurrent writers that are not excluded: ath_intr ISRs already past
`ath_hal_intrpend` (same argument as DF-3617 — intrset(0) at 1785 does not
wait), and the taskqueue thread executing ath_tx_proc*/ath_txq_sched_tasklet.
ath_transmit itself is serialized by net80211 comlock against vap delete,
but the driver's deferred TX machinery is not.

## Preconditions
Atheros NIC, active TX (or vap-detach-queued frames), SMP, admin destroys a
vap (ifconfig destroy / cloning teardown). The race is timing-dependent —
speculative on triggerability, author-acknowledged in the source comment.

## Impact if hit
Use of freed `struct ath_vap` (mcastq linkage, completion callbacks) →
kernel UAF read/write → panic at minimum; slab-reuse dependent for anything
worse.

## Why inconclusive here
Guest has no ath hardware (env.txt) — ath_vap_delete on an ath vap cannot
be driven; the race additionally needs SMP timing under TX load.

## Fix
fix.diff is a targeted mitigation: block+drain the driver taskqueue tasks
between the second drain and the kfree, so no deferred task can still hold
vap state when it is freed. (Upstream later reworked this whole path; the
minimal drain closes the specific window the comment identifies.)
