# DF-5246 — if_run(4) `run_vap::beacon_mbuf` write-after-free race

`sys/bus/u4b/wlan/if_run.c`

## What

`run_update_beacon()` accesses `rvp->beacon_mbuf` while holding only the
net80211 `ic` lock; `run_newstate()` (RUN→INIT / RUN-entry) and
`run_vap_delete()` free the same pointer while holding only the driver
`sc` lock. The two lock sets are disjoint, so the NULL-check
(if_run.c:4887) races against the free (if_run.c:2105-2106 / 1013-1014),
and `ieee80211_beacon_update()` (if_run.c:4892) then writes through the
stale pointer — write-after-free on a kernel mbuf cluster.

## Simulated reproduction (hardware-gated: needs a real run(4) USB adapter)

The guest has `if_run.ko` and the driver loads, but the QEMU guest has no
USB controller / no Ralink device, so the in-kernel path cannot be
exercised (QEMU cannot emulate a RT2870 vendor-protocol WLAN device).

`beacon_race.c` instead models the *exact* code regions and lock domains
(two pthread mutexes standing in for `IEEE80211_LOCK(ic)` and
`RUN_LOCK(sc)`) and shows the interleaving is trivially reachable:

    build:  cc -O2 -pthread -o beacon_race beacon_race.c   (build.sh)
    run:    ./beacon_race 10                               (run.sh, 3 runs)

Observed on the guest (6 vCPU, DF 6.5-DEVELOPMENT):

    run 1: 343,159 UAF hits / 92.5M clean updates
    run 2: 383,831 UAF hits / 99.2M clean updates
    run 3: 443,967 UAF hits /122.0M clean updates

i.e. ~0.3–0.4 % of all beacon updates would have written into freed
memory when a state transition / vap destroy lands concurrently.
Every hit is, in the kernel, `ieee80211_beacon_update()` writing
`wh->i_seq` (offset 22), `bo_caps`, TIM bytes — and on the CSA path a
full-beacon rewrite via `ieee80211_beacon_construct()` — into a freed
mbuf cluster.

## Verdict

`inconclusive` (simulated only; kernel path is hardware-gated),
confidence `likely`: the missing-lock-intersection is proven statically
from the source; only the dynamic confirmation needs the hardware.
