# DF-3470 — net80211 ratectl registry has no in-use accounting: kldunload of a
# rate-control algorithm in use leaves every vap's `iv_rate` dangling into
# unmapped module text

## What

`sys/netproto/802_11/wlan/ieee80211_ratectl.c` keeps
`static const struct ieee80211_ratectl *ratectls[IEEE80211_RATECTL_MAX]`
and hands **raw pointers** (`vap->iv_rate = ratectls[type]`,
ieee80211_ratectl.c:152) to vaps, which are then invoked through
unprotected inline wrappers on hot paths
(`ieee80211_ratectl.h:69-124`: `ir_rate` on every TX,
`ir_node_init` on association, `ir_node_stats` via the **world-readable**
`net.wlan.<n>.rate_stats` sysctl handler at ieee80211_ratectl.c:127-129 /
88-116, `ir_deinit` during vap detach).

`ieee80211_ratectl_unregister()` (ieee80211_ratectl.c:66-71) blindly NULLs
the registry slot; it neither checks for nor prevents unload while vaps
still reference the algorithm. The intended guard exists in the module
glue — `_IEEE80211_POLICY_MODULE`
(sys/netproto/802_11/ieee80211_dragonfly.h:316-336) refuses MOD_UNLOAD
with EBUSY when `nrefs != 0` — but the rate-control algorithm modules
never increment it:

* `sys/netproto/802_11/wlan/ieee80211_amrr.c:77` — `static int nrefs = 0;`
  and **nothing else** (grep: single occurrence).
* `sys/netproto/802_11/wlan/ieee80211_rssadapt.c:97` — same.
* `sys/netproto/802_11/wlan/ieee80211_ratectl_none.c:96` — same.

Contrast with the modules that got the pattern right:
`sys/netproto/802_11/wlan_wep/ieee80211_crypto_wep.c:108,118-119`
(`nrefs++` / `nrefs--` in attach/detach) and
`sys/netproto/802_11/wlan/ieee80211_scan_sta.c:178,195-196`.
amrr is the **default** algorithm (ieee80211_ratectl.c:121-122).

## Consequence

`kldunload wlan_amrr` (or wlan_rssadapt / a module-built wlan_none) while
any vap exists succeeds (nrefs==0), unmaps the module, and leaves
`vap->iv_rate` pointing into unmapped memory. The next rate-control
operation — an unprivileged `sysctl net.wlan.0.rate_stats` read, any
packet TX, a new association, or vap teardown — performs an indirect call
through the dangling table: deterministic panic (execute-after-unmap). If
the vacated module address range is subsequently reoccupied by another
kldload, the call target becomes attacker-influenced module content.
amrr is the default ratectl, so *every* vap on a system where wlan_amrr
is a loadable module is exposed.

## Verification status on the guest

Code-certain, runtime-not-demonstrable on this guest, honestly
inconclusive:

* the guest kernel has wlan_amrr **compiled in**
  (`kldstat -v` lists it under kernel id 1; `kldunload wlan_amrr` ->
  "can't find file"), so the unload cannot even be attempted;
* the guest has **no 802.11 hardware**, so no vap can be created to hold
  `iv_rate = &amrr` (see VERDICT.md).

The mechanism is fully established by source: the guard exists, the
increment is missing in exactly the three rate-control modules, and the
registry (this file) provides no backstop.

## Files

* `analysis.txt` — the decisive greps (guest + tree)
* `VERDICT.md` — full narrative
* `verdict.json` — machine verdict (status: inconclusive)
