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

Beacon mbuf write-after-free across disjoint lock domains: rum_update_beacon's unlocked ieee80211_beacon_update window vs m_freem in rum_newstate/rum_vap_delete

Summary

update_beacon drops RUM_LOCK then beacon_update writes into bcn_mbuf's 2KB cluster holding only the ic lock, while newstate frees/replaces under RUM_LOCK (ic released) and vap_delete frees with NO lock - the ic and sc lock sets never overlap on bcn_mbuf. Guest 3-thread lock-domain model: freed-in-window UAF writes 1-13 per 10s across 6/6 runs (userspace floor; the in-kernel TIM/element recompute window is microseconds and preemptible). Fourth member of the beacon family (DF-5246/5254/5259). Hold-lock-across-write + lock-the-vap_delete-free fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5292 Β· 10 files
FileTypeDescriptionSize
rum_beacon_race.c β€” 5.7 KB view raw
build.sh β€” 113 B view raw
run.sh β€” 108 B view raw
build.log β€” 16 B view raw
run.log β€” 382 B view raw
env.txt β€” 241 B view raw
VERDICT.md β€” 3.4 KB ↓ raw
README.md β€” 2.1 KB ↓ raw
fix.diff β€” 753 B view raw
verdict.json β€” 4.0 KB view raw

DF-5292 β€” rum beacon mbuf write-after-free across disjoint lock domains

What

rum_update_beacon() (sys/bus/u4b/wlan/if_rum.c:2792-2831) drops RUM_LOCK at :2825 and then calls ieee80211_beacon_update(ni, m, mcast) at :2828, which WRITES seqno/capinfo/TIM/ERP element data into rvp->bcn_mbuf's 2 KB cluster while holding only the net80211 ic lock (net80211/wlan/ieee80211_output.c:3339 ff.). Two other paths free or replace that same cluster under disjoint lock domains:

  • rum_newstate() β€” releases IEEE80211_LOCK(ic) at :932, takes RUM_LOCK(sc) at :933, and on S_RUN rum_alloc_beacon() (:977 β†’ :2775-2778) does m_freem(rvp->bcn_mbuf) + replace, holding only the sc lock.
  • rum_vap_delete() β€” m_freem(rvp->bcn_mbuf) at :715 with NO lock at all.

ic-lock vs sc-lock are disjoint β‡’ a free landing between :2825 and the end of the in-mbuf writes is a kernel write-after-free on a freed 2 KB cluster.

Twin of DF-5246 (if_run), DF-5254 (if_urtwn), DF-5259 (if_mtw).

Hardware gating

All rum(4) paths need an RT2501/RT2601 USB dongle to attach; the QEMU guest has none, so in-kernel reproduction is not possible. Evidence is (a) the line-level lock-domain trace above and (b) a userland interleaving model of the exact code regions and locks (rum_beacon_race.c), run 3Γ—10 s on the guest. The struct/module if_rum.ko was kldload'ed on the guest to confirm the audited code is the running build (md5-identical source tree).

Build / Run / Expected

cc -O2 -pthread -Wall -o rum_beacon_race rum_beacon_race.c
./rum_beacon_race 10        # 3 runs

Expected: freed-in-window UAF WRITES > 0 in every run (reachability of the interleave); see run.log (1, 9, 3 hits in the first series; 5, 2, 13 in the preemption-mode series β€” the userspace floor; the in-kernel window is microseconds of TIM/element recompute and is unbounded under preemption).

Fix

fix.diff β€” hold RUM_LOCK across the beacon_update in-mbuf writes (and take it around the m_freem in rum_vap_delete). git apply --check clean. Kernel fix validation is hardware-gated (fix_status: not_testable).

VERDICT.md
↓ download raw

DF-5292 VERDICT β€” beacon mbuf write-after-free (rum twin of DF-5246/5254/5259)

Status: inconclusive (hardware-gated). Bug: real, line-proven.

Reproduction path (in-kernel)

  1. Attach rum(4) device, create wlan0 in hostap or ibss mode, bring it up (S_RUN allocates the beacon via rum_alloc_beacon).
  2. Drive iv_update_beacon (remote-assistable: TIM updates on station sleep/wake, ERP changes, CSA; local: ioctl-triggered beacon changes) β€” this enters rum_update_beacon on a net80211/serializer thread holding only the ic lock after :2825.
  3. Concurrently force the other domain: ifconfig wlan0 down / re-run (rum_newstate β†’ S_RUN β†’ rum_alloc_beacon β†’ m_freem(old bcn) under sc lock, :932-933/:2775-2778) or ifconfig wlan0 destroy (rum_vap_delete β†’ m_freem at :715, no lock).
  4. A free landing between :2825 and the end of ieee80211_beacon_update's writes corrupts the freed 2 KB cluster (write-after-free).

Why inconclusive on this guest

No USB passthrough, no RT2573 device model β‡’ rum(4) never attaches β‡’ the vap/beacon lifecycle cannot be entered. (Confirmed: source md5 on guest == audit tree; kldload if_rum.ko works; no hardware probe.)

Simulated evidence

rum_beacon_race.c models the three threads with the exact lock protocol (ic mutex, sc mutex; deleter lock-free). Results (guest, 3Γ—10 s): 1/9/3 UAF-write interleavings (base), 5/2/13 (with a mid-write yield modeling scheduler preemption) β€” nonzero in 6/6 runs β‡’ the interleaving is reachable; the userspace write burst is nanoseconds, while the in-kernel ieee80211_beacon_update performs TIM bitmap recopy, trailer memmove and element rebuilds (Β΅s, preemptible), so the real window is strictly larger.

Exploit chain (if hardware present)

hostap/ibss vap + power-save client TIM churn (remote-peer triggerable) racing ifconfig down/destroy β†’ ieee80211_beacon_update writes into a freed 2 KB cluster β†’ heap corruption β†’ panic; with post-free reallocation control (2 KB zone), controlled overwrite β†’ potential privesc.

Fix

Hold RUM_LOCK(sc) across the beacon_update write phase and around the m_freem in rum_vap_delete (fix.diff; git apply --check clean). Lock-order note: the fix takes sc while ic is held (ic→sc), which is the established direction in this driver (rum_update_promisc, rum_wme_update); RUM_LOCK is LK_CANRECURSE so the nested rum_set_channel path inside CSA completion is safe. Not kernel-testable without hardware.

Kernel references

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; kernel-testing is impossible on this guest (no USB passthrough / RT2573 device model), so neither baseline nor patched kernel behavior is exercisable. Hardware-gated: not_testable.

fix.diff (git-apply clean); VERDICT.md lock-order note (ic->sc is the established direction; RUM_LOCK is LK_CANRECURSE)
↓ fix.diffper-fix-DF-5292

Confirmed kernel references

Detail

Exploit chain

hostap/ibss vap + power-save client TIM churn / ERP/CSA beacon updates (remote-peer assistable) racing ifconfig down/destroy or an S_RUN re-entry -> ieee80211_beacon_update writes seqno/capinfo/TIM bitmap into a freed 2KB mbuf cluster -> heap corruption -> panic; with post-free reallocation control of the 2KB zone -> controlled overwrite -> privesc potential

Evidence (decisive lines)

run.log: 'freed-in-window UAF WRITES=1/9/3' then '5/2/13' across 3+3 runs (nonzero every run); rum_beacon_race.c models :2800/:2803-2825/:2828 vs :932-933/:2775-2776 vs :715 with two mutexes and a lock-free deleter; VERDICT.md lock-intersection table

PoC changes

Harness written from scratch (no seed); pthread mutexes model IEEE80211_LOCK(ic) and RUM_LOCK(sc); the deleter thread is deliberately lock-free exactly like rum_vap_delete:715; an atomic poison flag counts would-be UAF writes without killing the run; a sched_yield variant models preemption

Verified recommended fix

Hold RUM_LOCK across the beacon_update in-mbuf writes in rum_update_beacon() and around the m_freem in rum_vap_delete()

Verdict

Beacon-mbuf write-after-free, rum's instance of the run/urtwn/mtw twin family (DF-5246/DF-5254/DF-5259). rum_update_beacon() (if_rum.c:2792-2831) drops RUM_LOCK at :2825 and ieee80211_beacon_update() at :2828 writes seq/capinfo/TIM/ERP data into rvp->bcn_mbuf's 2KB cluster under the net80211 ic lock only, while rum_newstate() (:932-933 drops the ic lock, takes RUM_LOCK; S_RUN -> rum_alloc_beacon :977 -> m_freem at :2775-2776) and rum_vap_delete() (m_freem at :715 with NO lock) free the same cluster in disjoint lock domains. IEEE80211_LOCK is a per-ic lockmgr mutex (ieee80211_dragonfly.h:598), not a global serializer, so the domains really are disjoint. In-kernel reproduction is hardware-gated (no RT2573 device can attach to the QEMU guest; audited source is md5-identical to the guest tree and if_rum.ko loads). Simulated on the guest with a line-faithful 3-thread lock-domain model: freed-in-window UAF writes 1/9/3 (base) and 5/2/13 (with preemption yield) per 3x10s runs - reachable in 6/6 runs; the userspace write burst is the optimistic floor, the in-kernel TIM/element recompute window is microseconds and unbounded under preemption.