Beacon mbuf use-after-free write: urtwn_update_beacon's unlocked ieee80211_beacon_update window races setup_beacon/vap_delete across disjoint lock domains
Summary
update_beacon RELEASES sc_mtx around ieee80211_beacon_update (TIM/CSA writes INTO the mbuf under only the com lock) while setup_beacon frees/swaps under sc (com lock dropped at :2551) and vap_delete frees with NO lock at all - write-after-free on a 2KB mbuf cluster at attacker-paced frequency. IBSS variant remotely triggerable: crafted beacons with larger TSF -> ibss_merge -> newstate(S_RUN) -> setup_beacon free/swap, timed against PS-traffic TIM writes. Twin of the if_run.c DF-5246 family. Guest race_sim (exact lock choreography, zone-allocator emulation, unpriv user): 6/6 genuine write-after-frees incl. the vap_delete variant; hardware-gated -> inconclusive. Hold-lock-across-update + lock-the-vap_delete-free fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5254 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.7 KB | β raw | |
| VERDICT.md | β | 5.3 KB | β raw | |
| race_sim.c | β | 7.6 KB | view raw | |
| build.sh | β | 99 B | view raw | |
| run.sh | β | 93 B | view raw | |
| build.log | β | 230 B | view raw | |
| run.log | β | 3.4 KB | view raw | |
| run.2.log | β | 669 B | view raw | |
| env.txt | β | 343 B | view raw | |
| fix.diff | β | 979 B | view raw | |
| manifest.json | β | 1.0 KB | view raw | |
| verdict.json | β | 4.3 KB | view raw |
DF-5254 β urtwn beacon mbuf use-after-free (disjoint lock domains)
What
urtwn_update_beacon() manipulates uvp->bcn_mbuf with the driver mutex
(sc_mtx) released while it calls ieee80211_beacon_update()
(sys/bus/u4b/wlan/if_urtwn.c:2206-2212). Two other paths free/replace that
same mbuf under different (or no) lock domains:
urtwn_setup_beacon()βm_freem(uvp->bcn_mbuf); uvp->bcn_mbuf = m;(if_urtwn.c:2168-2171), reached fromurtwn_newstate()after it drops the net80211 com lock (IEEE80211_UNLOCK(ic), if_urtwn.c:2551) and takessc_mtx. Runnable S_RUN re-entry is attacker-reachable in IBSS mode: a crafted beacon with a larger TSF triggersieee80211_ibss_merge()βieee80211_sta_join1()β full state-machine re-entry β RUN (netproto/802_11/wlan/ieee80211_node.c:610-630).urtwn_vap_delete()βm_freem(uvp->bcn_mbuf)with no lock at all (if_urtwn.c:840-841).
Meanwhile iv_update_beacon is invoked with the com lock held
(e.g. ieee80211_set_tim(), netproto/802_11/wlan/ieee80211_power.c:310-318,
driven by station power-save traffic β i.e. remotely paced) but not
sc_mtx, and ieee80211_beacon_update() writes TIM/CSA fields into the
mbuf. No lock orders the writer (A) against the freer (B/C):
A holds com, B holds sc, C holds nothing β classic use-after-free write
on an mbuf cluster (m_freem returns the cluster to the mbuf cluster pool
immediately).
Reproduce
Hardware-gated on-guest (no RTL8188CUS/EU USB device attached to the QEMU
guest, kldstat shows urtwn not loaded), so this pack contains a faithful
two-lock-domain race simulation of the exact interleaving instead:
./build.sh # cc -O2 -pthread -Wall -o race_sim race_sim.c ./run.sh # 3-5 runs x 5s; exit code 3 == UAF write detected
Expected (observed 6/6 clean runs on the guest, unprivileged user):
[UAF-WRITE] thread A wrote into a freed beacon mbuf (gen NNN) -- freed by concurrent setup_beacon/vap_delete while A was inside ieee80211_beacon_update() with sc_mtx released (if_urtwn.c:2206-2212) RESULT: USE-AFTER-FREE WRITE reproduced exit=3
run.2.log shows the vap_delete variant firing (C frees = 1).
Fix
See fix.diff: hold URTWN_LOCK(sc) across ieee80211_beacon_update()
(matching the driver's own locking for urtwn_tx_beacon), and take
URTWN_LOCK(sc) around the free in urtwn_vap_delete().
Files
race_sim.cβ pthread model of the interleaving (zone-allocator emulation, generation counters; stale-generation write == write-after-free)build.sh,run.sh,build.log,run.log,run.2.log,env.txtfix.diff,manifest.json,verdict.json
DF-5254 β VERDICT
Finding: use-after-free write on the urtwn beacon mbuf
(struct urtwn_vap.bcn_mbuf) because urtwn_update_beacon() releases the
driver mutex around ieee80211_beacon_update() while
urtwn_setup_beacon() / urtwn_vap_delete() free-and-replace the same
mbuf from lock domains that are not ordered against it.
Reproduced?
On-guest kernel impact: NOT reproduced β hardware-gated. The guest has
no Realtek RTL8188CUS/EU/8192CU USB device (kldstat shows if_urtwn.ko
present at /boot/kernel/if_urtwn.ko but not loaded; no USB WLAN NIC in
usbdevs). urtwn_attach() cannot run, so the kernel race cannot be fired
directly. Per the audit protocol this is recorded as inconclusive with
strong simulated evidence.
Simulated evidence: reproduced 6/6 clean runs (100%). race_sim.c
models the exact interleaving with three pthreads mirroring the three
code paths:
| sim thread | kernel path | locks modelled |
|---|---|---|
A thread_update_beacon |
ieee80211_set_tim (com held) β urtwn_update_beacon if_urtwn.c:2196-2216 |
com held; sc taken/dropped exactly at 2206/2214 |
B thread_newstate |
IBSS merge β urtwn_newstate (com dropped at 2551) β urtwn_setup_beacon 2168-2171 |
sc held, com dropped |
C thread_vap_delete |
urtwn_vap_delete if_urtwn.c:840-841 |
no locks |
A zone-allocator emulation (free-list + generation counters) marks a
cluster dead at m_freem(); any subsequent write is a genuine
write-after-free. Observed on guest (DragonFly 6.5-DEVELOPMENT, gcc 8.3,
6 vCPU, unprivileged user):
run.log 5/5 runs exit=3 (UAF write detected; gens 220,184,186,404,403) run.2.log 1/1 exit=3, killer = thread C (vap_delete variant, "vap_delete frees (C): 1")
~200 newstate re-entries and ~5000 update iterations per 5s run are enough
to hit the window every time; on real hardware the B path is paced by
remote IBSS merge beacons and the A path by station power-save TIM updates
(both attacker-controllable timing), so the window (the whole duration of
ieee80211_beacon_update() on the old pointer, if_urtwn.c:2212) is
realistic.
Why it is a bug (source trace)
sys/netproto/802_11/wlan/ieee80211_power.c:310-318βieee80211_set_tim()holdsIEEE80211_LOCK(ic)(com) and callsvap->iv_update_beacon(vap, IEEE80211_BEACON_TIM).sys/bus/u4b/wlan/if_urtwn.c:2196-2216βurtwn_update_beacon(): takessc_mtx(2196), ensuresuvp->bcn_mbuf != NULL(2197-2205), releasessc_mtx(2206), thenieee80211_beacon_update(ni, uvp->bcn_mbuf, mcast)(2212) writes into the mbuf, then re-takessc_mtx(2214) forurtwn_tx_beacon.sys/bus/u4b/wlan/if_urtwn.c:2551-2552βurtwn_newstate()drops the com lock before takingsc_mtx, so its call intourtwn_setup_beacon()(2161-2171:m_freem(uvp->bcn_mbuf); uvp->bcn_mbuf = m;) is not serialized against step 2 by either lock: A holds com (not sc), B holds sc (not com).sys/bus/u4b/wlan/if_urtwn.c:2667-2676β setup_beacon runs for both HOSTAP and IBSS opmodes on every S_RUN entry; IBSS S_RUN re-entry is remotely triggerable viaieee80211_ibss_merge()βieee80211_sta_join1()(netproto/802_11/wlan/ieee80211_node.c:630).sys/bus/u4b/wlan/if_urtwn.c:840-841βurtwn_vap_delete()freesuvp->bcn_mbufwith no lock whatsoever (Race variant C).
ieee80211_beacon_update() writes TIM bitmap / seq / CSA values into the
mbuf (netproto/802_11/wlan/ieee80211_output.c:3339+), i.e. the
use-after-free is a write primitive into a freed mbuf cluster
(2 KB), repeated at attacker-paced frequency. The twin of this bug family
was confirmed in the if_run.c pass-2 audit (beacon lifecycle /
disjoint-lock-domain).
Exploitability sketch (why High)
- Object: mbuf cluster (2 KB) in a global cluster pool β freed clusters are recycled aggressively on any network load, so the write lands in a live adjacent object quickly (cross-object corruption, not a write-to-free-page).
- Control: attacker controls when (TIM update pacing via PS traffic + merge beacons) and content partially (TIM bitmap bytes, beacon contents from the vap's own beacon template).
- Gate: needs a urtwn USB NIC in hostap/ibss operation; attacker is a nearby peer (IBSS: fully unauthenticated).
- Ceiling: kernel memory corruption from an unauthenticated remote peer β panic now, potentially more with cluster-pool grooming.
Fix validation
Not kernel-tested (no hardware on the guest). fix.diff keeps
URTWN_LOCK(sc) held across ieee80211_beacon_update() and locks the
free in urtwn_vap_delete(); the simulated harness run with the fix's
locking (sc held across A's write phase) shows 0 detections by
construction, since B/C can no longer free while A writes.
Bottom line
status: inconclusive (hardware-gated kernel PoC), reproduced=false, impact on-guest=none; the code-level defect is certain to exist from the source trace and the simulated interleaving reproduces the UAF write in 6/6 runs. Recommended severity: High (memcorrupt bucket).
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 is git-apply-able against sys/ and removes the unlocked window by construction (B/C serialize on sc_mtx with the writer); kernel build+device test not possible on this guest (no USB WLAN hardware) so not_testable
findings/poc/DF-5254/fix.diff (git apply --check PASSED); locking argument in VERDICT.md
Confirmed kernel references
Detail
Exploit chain
unauthenticated remote peer (IBSS) or local admin-triggered state churn -> (1) attacker sends crafted beacons with larger TSF to force ieee80211_ibss_merge -> newstate(S_RUN) re-entry -> urtwn_setup_beacon m_freem(bcn_mbuf) under sc_mtx; concurrently (2) station power-save traffic drives ieee80211_set_tim -> urtwn_update_beacon -> ieee80211_beacon_update writes TIM/CSA bytes into the freed 2KB mbuf cluster -> cross-object heap corruption (cluster pool recycles immediately) -> crash today, controlled-content write into recycled kernel object with pool grooming. Simulated chain proven at interleaving level (exit=3, 6/6); hardware chain not exercised.
Evidence (decisive lines)
race_sim.c + run.log (5/5 runs exit=3 UAF write detected, gens 220/184/186/404/403), run.2.log (vap_delete variant, C frees=1), VERDICT.md source trace with path:line for all three racing paths, fix.diff (git apply --check clean)
PoC changes
No kernel PoC possible (hardware-gated); built race_sim.c from scratch: pthread model of the exact lock choreography (com held across update path, sc dropped at 2206, com dropped at 2551, no-lock vap_delete), zone-allocator emulation with generation counters so a stale-generation write is a true write-after-free. Iterations: fixed aligned_alloc size-misuse crash, made vap_delete thread leave the field dangling (as the kernel does) instead of NULLing it.
Verified recommended fix
Hold URTWN_LOCK(sc) across ieee80211_beacon_update() in urtwn_update_beacon() and take URTWN_LOCK(sc) around m_freem(uvp->bcn_mbuf) in urtwn_vap_delete() (see fix.diff)
Verdict
Use-after-free WRITE on struct urtwn_vap.bcn_mbuf: urtwn_update_beacon() releases sc_mtx around ieee80211_beacon_update() (if_urtwn.c:2206-2212, writer holds only the net80211 com lock via iv_update_beacon callers such as ieee80211_set_tim) while urtwn_setup_beacon() frees-and-swaps the same mbuf under sc_mtx with the com lock dropped (urtwn_newstate if_urtwn.c:2551-2552, setup at 2168-2171; IBSS S_RUN re-entry is remotely triggerable via ieee80211_ibss_merge->ieee80211_sta_join1), and urtwn_vap_delete() frees it with no lock at all (840-841). No lock orders writer against freer. On-guest kernel PoC is hardware-gated (no RTL8188 USB NIC on the QEMU guest; if_urtwn.ko present but unloadable without a device), so kernel-level reproduction is honestly recorded as inconclusive; a faithful three-thread two-lock-domain simulation of the exact interleaving reproduced the write-after-free in 6/6 runs on the guest as an unprivileged user, including the vap_delete variant (run.2.log). Severity High (memcorrupt), twin of the if_run.c pass-2 beacon-lifecycle family.
No comments yet.