# DF-5246 VERDICT — beacon_mbuf write-after-free race

## Finding

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

- `run_update_beacon()` — **if_run.c:4861-4901**
  - NULL-check + alloc: 4887-4891
  - use: `ieee80211_beacon_update(ni, rvp->beacon_mbuf, mcast)` — 4892
  - deferred re-arm: 4894-4898
- `run_newstate()` — **if_run.c:2048-2180**
  - drops the ic lock, takes only the sc lock: 2067-2068
  - `m_freem(rvp->beacon_mbuf); rvp->beacon_mbuf = NULL;` — **2105-2106**
    (executed on *every* entry to `IEEE80211_S_RUN` and exit RUN→INIT)
- `run_vap_delete()` — **if_run.c:997-1031**
  - `m_freem(rvp->beacon_mbuf)` under the sc lock only: **1013-1014**

Callers that reach `run_update_beacon` with *only* the ic lock held:

- `ieee80211_set_tim()` — sys/netproto/802_11/wlan/ieee80211_power.c:290-319
  (`IEEE80211_LOCK(ic)` … `vap->iv_update_beacon(vap, IEEE80211_BEACON_TIM)`),
  invoked from the TX path (`ieee80211_pwrsave`) for every power-save
  buffered-frame TIM change in hostap/ibss mode — i.e. continuously,
  attacker-influenced (a PS client triggers it remotely).
- `ieee80211_beacon_notify()` callers (ERP/HT/CSA/WME updates):
  sys/netproto/802_11/ieee80211_var.h:865, ieee80211_node.c:2444,
  ieee80211_proto.c:1278/1769, ieee80211_ht.c:1288 — all under
  `IEEE80211_LOCK`.

The write side: `ieee80211_beacon_update()` (sys/netproto/802_11/wlan/
ieee80211_output.c:3339+) writes `wh->i_seq`, `*bo->bo_caps`, TIM, and on
the CSA path rewrites the entire beacon via `ieee80211_beacon_construct()`
through the passed-in mbuf — a write-after-free into a freed mbuf cluster
(MCLEX cluster / pkthdr mbuf, typically 2 KB heap object).

Lock sets at the two conflicting accesses:

| path                    | locks held        | touches beacon_mbuf   |
|-------------------------|-------------------|-----------------------|
| run_update_beacon       | {ic}              | read + write-through  |
| run_newstate            | {sc} (ic dropped) | free + NULL           |
| run_vap_delete          | {sc}              | free + NULL           |

Intersection is empty ⇒ no serialization ⇒ classic check-then-use race.

## Why it is reachable

- hostap/ibss mode on run(4) (`ifconfig wlan0 create ... mode hostap`,
  hostapd) is a stock configuration;
- TIM updates fire from the TX path of *other* threads while the net80211
  state machine task (`iv_nstate_task`, sys/netproto/802_11/wlan/
  ieee80211_proto.c:2224) executes `run_newstate` — true concurrency on
  SMP;
- the free happens on **every** RUN transition (2105 runs when *entering*
  RUN too, immediately before `run_update_beacon_cb` reallocates) and on
  every vap destroy.

## Simulated verification

See README.md. `beacon_race.c` models the two lock domains and the exact
check/use/free regions. Result on the guest (3×10 s): 343k / 384k / 444k
write-after-free interleavings per run — the window is hit on ~0.35 % of
beacon updates. In-kernel this means a single RUN↔INIT transition or
`ifconfig wlanX destroy` concurrent with beacon traffic has a high
probability of corrupting freed heap memory (contents written: 802.11 seq
numbers, capinfo, TIM bitmap, and on CSA a full beacon frame — partially
attacker/attacker-peer influenced).

## Status

- `status: inconclusive` — the kernel path itself is hardware-gated (no
  Ralink USB device can be attached to the QEMU guest; `usbconfig` shows
  no USB controller, and QEMU has no RT2870-class device model). The
  simulation is faithful to the code but is not an in-kernel repro.
- `impact: none (observed)`; the finding itself is kernel heap
  write-after-free (severity High, bucket memcorrupt).
- `reproduced: 0` per the honest-classification table (no in-guest kernel
  manifestation possible).

## Exploit chain (what the class permits, hardware permitting)

1. run(4) AP in hostap mode with power-save clients (TIM updates fire
   continuously) — all remote-802.11-peer triggerable.
2. Force `run_newstate` RUN→INIT or a vap destroy at a chosen moment
   (ifconfig down/destroy, deauth-driven state change) while TIM churn is
   high.
3. `ieee80211_beacon_update` writes into the freed 2 KB mbuf cluster
   between the free and reallocation.
4. Heap grooming (mbuf clusters are a predictable slab) turns the stale
   write into corruption of a re-allocated victim object; the CSA path
   gives a large, structured overwrite. Realistic ceiling: kernel memory
   corruption → panic → potential privesc with sufficient control of
   post-free reallocation.

## Recommended fix

Serialize all `beacon_mbuf` accesses on the sc lock: have
`run_update_beacon()` take `RUN_LOCK` around the check/alloc/update
(usb-control work already happens under the sc lock elsewhere), or move
the free in `run_newstate`/`run_vap_delete` behind the same lock the
update path holds (see fix.diff).

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