Deferred run_cmdq entries outlive the vap/node they point at: run_vap_delete frees run_vap without draining sc->cmdq_task
Summary
8 producer sites stash raw vap/ni/key pointers in sc->cmdq[] deferred via ieee80211_runtask; vap destroy kfree's rvp but drains only iv_nstate_task/iv_swbmiss_task - sc->cmdq_task drains at device detach only. Pending entry executes run_update_beacon_cb/run_key_set_cb on freed heap (register writes from reallocated memory). Guest-verified deterministic ordering model ('UAF observed'); ifconfig destroy racing key/beacon/assoc activity. draintask-before-free fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5247 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.6 KB | β raw | |
| VERDICT.md | β | 3.4 KB | β raw | |
| vap_lifetime.c | β | 2.8 KB | view raw | |
| build.sh | β | 102 B | view raw | |
| run.sh | β | 98 B | view raw | |
| build.log | β | 377 B | view raw | |
| run.log | β | 144 B | view raw | |
| env.txt | β | 199 B | view raw | |
| fix.diff | β | 567 B | view raw | |
| manifest.json | β | 829 B | view raw | |
| verdict.json | β | 2.7 KB | view raw |
DF-5247 β pending run_cmdq entries outlive the vap/node they point at
What
run_update_beacon() (4894-4898), run_key_set() (2368-2375),
run_bulk_tx_callbackN() timeout path (3157-3161) and run_newassoc()
(2669-2675) stash raw vap / ieee80211_node pointers in
sc->cmdq[i] and defer execution via ieee80211_runtask().
When a vap is destroyed (ifconfig wlanX destroy β
wlan_clone_destroy β run_vap_delete, ieee80211_dragonfly.c:125-131),
run_vap_delete() frees the beacon mbuf and kfree(rvp, M_80211_VAP)
(if_run.c:1013, 1030). ieee80211_vap_detach() drains only
iv_nstate_task and iv_swbmiss_task
(sys/netproto/802_11/wlan/ieee80211.c:775-778) β it never drains the
driver's sc->cmdq_task / sc->ratectl_task. Those are drained only in
run_detach() (if_run.c:879-880), i.e. when the whole USB device goes
away.
A still-pending cmdq entry then executes after the free:
run_update_beacon_cb(vap) dereferences the freed run_vap
(4904-4952), run_key_set_cb dereferences the freed vap
(cmdq->arg1 = vap, 2237-2256), run_newassoc_cb dereferences a
possibly-freed node (2625-2639) β kernel use-after-free read/write.
Simulated reproduction
vap_lifetime.c models the exact ordering (queue β free β reuse β
deferred callback runs). Output on the guest:
run_update_beacon_cb: vap=0x800490ac0 beacon_mbuf=0x0 (already freed: yes) UAF observed: deferred callback dereferenced freed run_vap
The dynamic in-kernel repro needs a real run(4) adapter (beacon/key
activity) β hardware-gated on this QEMU guest β status inconclusive,
confidence likely.
DF-5247 VERDICT β deferred cmdq entries outlive the vap/node they reference
Ordering (all citations sys/bus/u4b/wlan/if_run.c unless noted)
T1 β producers stash raw object pointers into sc->cmdq[i] and defer:
- run_update_beacon() 4894-4898: arg0 = vap + ieee80211_runtask.
- run_key_set() 2368-2375: arg1 = vap, k = k (for pairwise keys
k == &ni->ni_ucastkey β node-embedded).
- run_newassoc() 2669-2675: arg1 = ni (raw node pointer).
- tx-timeout in run_bulk_tx_callbackN() 3157-3161: arg0 = vap.
T2 β vap destruction:
- wlan_clone_destroy() β ic->ic_vap_delete(vap) =
run_vap_delete() (sys/netproto/802_11/wlan/ieee80211_dragonfly.c:125-131;
if_run.c:997).
- run_vap_delete() frees beacon_mbuf (1013-1014) and later
kfree(rvp, M_80211_VAP) (1030).
- ieee80211_vap_detach() (sys/netproto/802_11/wlan/ieee80211.c:752+)
drains only iv_nstate_task and iv_swbmiss_task (775-778);
sc->cmdq_task/sc->ratectl_task are drained only in run_detach()
(879-880) β i.e. only when the USB device itself detaches.
T3 β the still-pending task fires after T2:
- run_update_beacon_cb(vap) 4904-4952 reads vap->iv_bss,
RUN_VAP(vap)->rvp_id, rvp->beacon_mbuf, mtod(m,...) β all freed.
- run_key_set_cb(&cmdq[i]) 2237-2352 derefs cmdq->arg1 (vap) and
cmdq->k (node-embedded key for pairwise).
- run_newassoc_cb 2625-2639 derefs cmdq->arg1 (node) and writes
sc->wcid_stats[wcid].
Any entry queued in the window "beacon/key/assoc activity" β
"ifconfig wlanX destroy" executes on freed heap. run_vap is
kmalloc(sizeof(struct run_vap), M_80211_VAP) (941) β a
ieee80211vap-sized (~2 KB) heap object; the deferred callback then
reads AND run_write_region_1's register data derived from freed
memory, and run_update_beacon_cb can call ieee80211_beacon_alloc
on the freed vap's iv_bss.
Simulated verification
vap_lifetime.c executes the exact T1βT2β(reuse)βT3 ordering in
userland (queue work item β free object β reuse chunk β run callback);
output (guest):
run_update_beacon_cb: vap=0x800490ac0 beacon_mbuf=0x0 (already freed: yes) UAF observed: deferred callback dereferenced freed run_vap
Status
inconclusive β in-kernel repro needs a real run(4) adapter driving
beacon/key activity and a timed vap destroy; the QEMU guest cannot
attach one. Confidence likely: the missing drain is provable from the
taskqueue drain sites; the ordering itself is deterministic once the
cmdq task is pending at destroy time (a wide window compared to the
usual micro-race, because vap teardown is slow: ether_ifdetach,
ieee80211_stop, sysctl teardown).
Exploit ceiling
Use-after-free of an M_80211_VAP heap object with heap-groomable reallocation (the guest's allocator is exploitable in principle); requires local root to time the destroy, so primarily a kernel reliability/memory-corruption bug β Medium.
Fix
Drain sc->cmdq_task + sc->ratectl_task in run_vap_delete() before
the free (fix.diff, 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.
Fix verification
not_testablefix.diff authored and verified with 'git apply --check' against the pristine sys/ tree, but cannot be kernel-tested: the QEMU guest has no USB controller and no run(4) device model exists, so neither baseline nor patched kernel behavior is exercisable. Hardware-gated: not_testable.
fix.diff (git-apply clean); VERDICT.md
Confirmed kernel references
Detail
Exploit chain
queue beacon/key/assoc cmdq entry (hostapd + remote peer activity) -> ifconfig wlanX destroy -> kfree(run_vap) + heap reuse -> cmdq task dereferences freed vap (read + device register writes derived from freed memory) -> kernel UAF; needs privileged destroy to arm, primarily reliability/corruption impact
Evidence (decisive lines)
findings/poc/DF-5247/run.log: 'UAF observed: deferred callback dereferenced freed run_vap'; ieee80211.c:775-778 drain list vs if_run.c:879-880 (cmdq drained only at device detach)
PoC changes
Harness written from scratch (no seed); ordering demo
Verified recommended fix
Drain sc->cmdq_task and sc->ratectl_task in run_vap_delete() before kfree(rvp)
Verdict
Deferred cmdq callbacks hold raw vap/node pointers that are not drained on vap destruction: ieee80211_vap_detach() drains only iv_nstate_task/iv_swbmiss_task (ieee80211.c:775-778), while run_vap_delete() (if_run.c:997-1031) kfree's rvp with sc->cmdq_task possibly still holding run_update_beacon/run_key_set/run_newassoc/tx-timeout entries pointing at it. The pending task then executes on freed heap (run_update_beacon_cb 4904-4952, run_key_set_cb 2237-2352, run_newassoc_cb 2625-2639). Ordering demonstrated deterministically by vap_lifetime.c on the guest; in-kernel repro hardware-gated -> inconclusive.
No comments yet.