# DF-5249 VERDICT — cmdq lockless handoff races

## Source facts (sys/bus/u4b/wlan/if_run.c)

- `RUN_CMDQ_GET(c)` = `atomic_fetchadd_32((c),1) & 15` — **if_run.c:92**
  (16-slot ring, `RUN_CMDQ_MAX`/`RUN_CMDQ_MASQ` if_runvar.h:225-227).
- Producers, **no lock**:
  - `run_key_set()` **2368-2375**: `func` first, then `arg0/arg1/k/mac`,
    then `ieee80211_runtask()`.
  - `run_key_delete()` **2443-2453** (same shape, copies key fields).
  - `run_newassoc()` **2669-2675** (stores raw `ni`).
  - tx-timeout path in `run_bulk_tx_callbackN()` **3157-3161**.
  - FIFO-drain deferral in `run_tx()` **3425-3429**.
  - `run_update_beacon()` **4894-4898**, `run_updateprot()` **4960-4964**,
    `run_updateslot()` **5206-5210**.
- Consumer `run_cmdq_cb()` **1041-1068** (under `RUN_LOCK`):
  `for (i = cmdq_exec; cmdq[i].func && pending; i = cmdq_exec, pending--)`
  then `func(arg0)` / `func(&cmdq[i])` — the `.func` read in the loop
  condition and the `.arg0` read in the body are **separate, unordered
  loads** against unlocked producers.

## Failure modes (all simulated in cmdq_race.c)

1. Torn publish (`func` visible, `arg0` not yet) → `run_update_beacon_cb(NULL)`
   → NULL deref panic.
2. Wrap overwrite between consumer loads (or of a pending slot) →
   `func_new(arg0_old)` type confusion: `run_key_set_cb` receives a
   `struct run_cmdq *` that is actually a `vap` (or vice versa), loads
   `cmdq->k`/`cmdq->key` from the wrong object → wild dereference,
   kernel memory disclosure into device register writes.
3. >16 outstanding commands → overwritten slot = silently lost command,
   e.g. lost `run_key_delete` leaves stale hardware keys.

## Simulation results (guest, 6 vCPU, 3×10 s)

    torn NULL-arg calls  : 12,011 / 13,347 per 10 s run
    identity mismatches  : 470,340 / 484,744
    slot wrap overwrites : 53.1M / 52.9M

The harness intentionally lags the consumer to widen windows; the point
proven is that each interleaving is *reachable*, not its production
rate. In-kernel the consumer is delayed by preemption, by the DF-5248
2.5 s control-request retries, and by >16 queued commands (beacon TIM
churn + key updates + tx timeouts), after which the same interleavings
occur.

## Status

`inconclusive` — no run(4) hardware on the QEMU guest (`usbconfig`:
no USB controller; QEMU has no RT2870 device model). The store/execute
order in the harness is copied verbatim from the source. Confidence
`likely`.

## Exploit ceiling

Type-confused callback argument → dereference of a `struct ieee80211_key *`
read from attacker-uninfluenced but wrong object → wild read used for
device register writes (`run_write_region_1(base, k->wk_key, 16)` reads
32 bytes from wherever `cmdq->k` points) → kernel memory disclosure to
the device / crash. Lost `run_key_delete` = stale hardware key
(crypto-relevant state machine failure).

## Fix

fix.diff reorders every producer to store `.func` **last** behind a
`cpu_ccfence()` compiler barrier, and makes the consumer take a single
structured pass over the slot (`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.
