# DF-3749 — VERDICT

**status: not_reproduced (hardware absent) — dangling-pointer defect source-certain;
deref window requires on-air timing, assessed likely**

## Proof of the dangling reference (static, indisputable)

- `rt2860_newassoc` rt2860.c:885-892: `sc->wcid2ni[wcid] = ni;` — no
  `ieee80211_ref_node`, no lifetime note. Borrowed pointer.
- `rt2860_node_free` rt2860.c:897-911: clears the **hardware** WCID entry
  (`RAL_SET_REGION_4(RT2860_WCID_ENTRY(wcid), 0, 2)`) and calls through to
  `sc->sc_node_free(ni)` which frees the node. `wcid2ni[wcid]` is left pointing at
  freed memory.
- Whole-driver grep for `wcid2ni`: only rt2860.c:887 (store), rt2860.c:1137 (load).
  Neither `rt2860_stop_locked` (4141-4174) nor any other path clears the array.
- `rt2860_drain_stats_fifo` rt2860.c:1133-1165 runs under RAL_LOCK from
  (a) MAC_INT_2 (rt2860_intr rt2860.c:1475-1476) and (b) the head of every
  `rt2860_tx_intr` (rt2860.c:1174) — i.e. after *any* TX activity, including traffic
  to other stations on a busy AP.
- Consumer chain at rt2860.c:1157-1163:
  `ieee80211_ratectl_tx_complete(ni->ni_vap, ni, IEEE80211_RATECTL_TX_{SUCCESS,FAILURE}, &retrycnt, NULL)`
  → `vap->iv_rate->ir_tx_complete(...)` (ieee80211_ratectl.h:99-103; `vap` itself was
  read from the freed node) → `amrr_tx_complete`
  (wlan/ieee80211_amrr.c:359-370): `amn = ni->ni_rctls` (pointer read out of freed
  memory) then `amn->amn_txcnt++; amn->amn_retrycnt += retries;` — **write through a
  freed-memory-derived pointer**; also `if_inc_counter(ni->ni_vap->iv_ifp, …)` at
  rt2860.c:1162.

## Why the window is real (not merely theoretical)

- TX descriptor completion (TXD DDONE writeback) and the MCU latching a TX_STAT_FIFO
  entry are independent hardware paths; the driver drains the FIFO *before* releasing
  the ring slot's node reference (rt2860_tx_intr:1174 drains, then :1188
  ieee80211_tx_complete drops the ring's ref), so a stat latched *after* that drain but
  *before/at* TXD completion is processed in a later interrupt with zero references
  held — `wcid2ni` is the only "reference" and it is not one.
- Frames with long retry sequences (up to long-retry-limit at low rates, hundreds of ms)
  complete long after the ring slot's TXD for earlier frames; a deauth received in
  between (rt2860_intr processes RX at :1481 *after* the first stats drain at :1475 but
  before tx_intr's for rings 0-4 at :1484-1497) frees the node mid-interrupt while a
  stat for it may still be in flight.
- Concrete single-interrupt interleaving: RX (deauth → node freed once the RX path's
  own ref drops at rt2860.c:1390) followed by TX_DONE_INT0 → drain at rt2860.c:1174
  picking up the just-latched stat for the dead station.
- Also reachable via `rt2860_stop_locked` → `rt2860_reset_tx_ring` dropping all
  `data->ni` refs (rt2860.c:587-590) while a stat sits in the FIFO — narrowed by the
  following MAC reset, but the FIFO and the reset are racy on real silicon.

The *pointer* dangles 100% of the time after every station teardown; only the
deref needs the timing above. Attacker cost: repeat (associate, ping flood,
deauth) cycles against a hostap ral(4) AP — cheap.

## Exploitability
`ni->ni_rctls` is read from freed M_80211_NODE memory; the attacker can reclaim the
chunk (same-size allocations: new association nodes with attacker-influenced fields
via assoc IEs — MAC, rates, HT caps — land in `ieee80211_node` members) and thereby
steer `amn` (write target: `amn_txcnt++`, `amn_retrycnt += n` — increment primitives
at fixed offsets) and `ni->ni_vap` (indirect call target `vap->iv_rate->ir_tx_complete`
— full control of the called pointer if vap is groomed). On this guest's
no-SMAP/no-SMEP kernel class that is a plausible code-execution path; not demonstrable
without the NIC.

## Why not reproduced
Guest has no Ralink NIC (env.txt: pciconf shows none; only vtnet0/lo0 exist).
The trigger is an on-air 802.11 exchange; cannot be synthesized on this VM.

## Fix
`fix.diff`: clear `sc->wcid2ni[wcid] = NULL` in `rt2860_node_free` (paired with the
hardware-entry clear that is already there). This is the same shape OpenBSD used for
the rt2860-family wcid2ni lifetime fix.

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