# DF-5247 VERDICT — deferred cmdq entries outlive the vap/node they reference

## Ordering (all citations sys/bus/u4b/wlan/if_run.c unless noted)

T1 — producers stash raw object pointers into `sc->cmdq[i]` and defer:
- `run_update_beacon()` **4894-4898**: `arg0 = vap` + `ieee80211_runtask`.
- `run_key_set()` **2368-2375**: `arg1 = vap`, `k = k` (for pairwise keys
  `k == &ni->ni_ucastkey` — node-embedded).
- `run_newassoc()` **2669-2675**: `arg1 = ni` (raw node pointer).
- tx-timeout in `run_bulk_tx_callbackN()` **3157-3161**: `arg0 = vap`.

T2 — vap destruction:
- `wlan_clone_destroy()` → `ic->ic_vap_delete(vap)` =
  `run_vap_delete()` (sys/netproto/802_11/wlan/ieee80211_dragonfly.c:125-131;
  if_run.c:997).
- `run_vap_delete()` frees `beacon_mbuf` (**1013-1014**) and later
  `kfree(rvp, M_80211_VAP)` (**1030**).
- `ieee80211_vap_detach()` (sys/netproto/802_11/wlan/ieee80211.c:752+)
  drains **only** `iv_nstate_task` and `iv_swbmiss_task` (**775-778**);
  `sc->cmdq_task`/`sc->ratectl_task` are drained only in `run_detach()`
  (**879-880**) — i.e. only when the USB device itself detaches.

T3 — the still-pending task fires after T2:
- `run_update_beacon_cb(vap)` **4904-4952** reads `vap->iv_bss`,
  `RUN_VAP(vap)->rvp_id`, `rvp->beacon_mbuf`, `mtod(m,...)` — all freed.
- `run_key_set_cb(&cmdq[i])` **2237-2352** derefs `cmdq->arg1` (vap) and
  `cmdq->k` (node-embedded key for pairwise).
- `run_newassoc_cb` **2625-2639** derefs `cmdq->arg1` (node) and writes
  `sc->wcid_stats[wcid]`.

Any entry queued in the window "beacon/key/assoc activity" →
"`ifconfig wlanX destroy`" executes on freed heap. run_vap is
`kmalloc(sizeof(struct run_vap), M_80211_VAP)` (941) — a
`ieee80211vap`-sized (~2 KB) heap object; the deferred callback then
reads AND `run_write_region_1`'s register data derived from freed
memory, and `run_update_beacon_cb` can call `ieee80211_beacon_alloc`
on the freed vap's `iv_bss`.

## Simulated verification

`vap_lifetime.c` executes the exact T1→T2→(reuse)→T3 ordering in
userland (queue work item → free object → reuse chunk → run callback);
output (guest):

    run_update_beacon_cb: vap=0x800490ac0 beacon_mbuf=0x0 (already freed: yes)
    UAF observed: deferred callback dereferenced freed run_vap

## Status

`inconclusive` — in-kernel repro needs a real run(4) adapter driving
beacon/key activity and a timed vap destroy; the QEMU guest cannot
attach one. Confidence `likely`: the missing drain is provable from the
taskqueue drain sites; the ordering itself is deterministic once the
cmdq task is pending at destroy time (a *wide* window compared to the
usual micro-race, because vap teardown is slow: ether_ifdetach,
ieee80211_stop, sysctl teardown).

## Exploit ceiling

Use-after-free of an M_80211_VAP heap object with heap-groomable
reallocation (the guest's allocator is exploitable in principle);
requires local root to time the destroy, so primarily a kernel
reliability/memory-corruption bug — Medium.

## Fix

Drain `sc->cmdq_task` + `sc->ratectl_task` in `run_vap_delete()` before
the free (fix.diff, `git apply` verified).

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