β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5247

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5247 Β· 11 files
FileTypeDescriptionSize
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

sys/bus/u4b/wlan/if_run.c

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.

VERDICT.md
↓ download raw

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_testable
baseline no→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-5247

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.