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

Deferred rum_cmdq entry holds a raw struct ieee80211vap *; rum_vap_delete() frees the rum_vap without draining sc->cmdq_task - rum_update_beacon_cb/rum_set_beacon execute on freed memory

Summary

update_beacon queues the RAW vap pointer into the 16-slot ring; vap_delete drains only ratectl_task before kfree(rvp) - cmdq_task never drained. Callback reads rvp->bcn_mbuf/iv_txparms and streams mtod bytes to the device on freed memory. Guest model: shipped 11,599-12,071 post-free callback executions per 10s (~61%); drained (fix) mode exactly 0. Twin of DF-5247/DF-5260. draintask-before-free fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5293 Β· 10 files
FileTypeDescriptionSize
rum_cmdq_uaf.c β€” 6.0 KB view raw
build.sh β€” 107 B view raw
run.sh β€” 156 B view raw
build.log β€” 16 B view raw
run.log β€” 871 B view raw
env.txt β€” 241 B view raw
VERDICT.md β€” 3.7 KB ↓ raw
README.md β€” 2.2 KB ↓ raw
fix.diff β€” 625 B view raw
verdict.json β€” 4.2 KB view raw

DF-5293 β€” deferred rum_cmdq entry holds a raw vap pointer past rum_vap_delete()

What

rum_update_beacon() queues a DEFERRED command carrying the RAW struct ieee80211vap *:

sys/bus/u4b/wlan/if_rum.c:2830
    rum_cmd_sleepable(sc, &vap, sizeof(vap), 0, rum_update_beacon_cb);

rum_cmd_sleepable() (:745-771) copies the pointer into cmdq[] and merely ieee80211_runtask()s β€” asynchronous. The taskqueue thread later runs rum_cmdq_cb (:723-743) β†’ rum_update_beacon_cb (:2784-2790) β†’ rum_set_beacon(sc, data->vap) (:2729), which dereferences rvp->bcn_mbuf (:2733) and vap->iv_txparms (:2744) and streams mtod(m)/m_pkthdr.len bytes to the device (:2754-2755).

rum_vap_delete() (:709-721) frees rvp->bcn_mbuf (:715) and kfree(rvp, M_80211_VAP) (:720) β€” it drains rvp->ratectl_task (:717) but never drains sc->cmdq_task nor purges cmdq entries referencing the vap. A queued rum_update_beacon_cb therefore executes on freed memory: UAF read of the rum_vap (M_80211_VAP, ~0x2c0 bytes) and wild rum_write_multi read from the freed mbuf, sized by freed-heap garbage.

Twin of DF-5247 (if_run) / DF-5260 (if_mtw). Note: unlike run/mtw's DF-5249/5262 twins, rum's cmdq ring itself IS properly locked (producers take RUM_CMDQ_LOCK at :753-766), and rum_key_set/rum_key_delete copy the key BY VALUE (:3021/:3038) so the DF-5250/5263 raw-key UAF does not apply β€” the vap pointer at :2830 is the one dangling reference.

Hardware gating

rum(4) needs an RT2573 USB dongle; the QEMU guest has none. Evidence is the line-level lifetime trace plus a userland model of the queue/taskqueue/destroy protocol (rum_cmdq_uaf.c), 3Γ—10 s per mode on the guest.

Build / Run / Expected

cc -O2 -pthread -Wall -o rum_cmdq_uaf rum_cmdq_uaf.c
./rum_cmdq_uaf 10 0    # shipped: no drain  -> POST-FREE rum_set_beacon > 0
./rum_cmdq_uaf 10 1    # fixed: draintask before kfree -> exactly 0

See run.log: shipped 11599/11849/12071 post-free executions of the cb; fixed mode 0/0/0.

Fix

fix.diff β€” ieee80211_draintask(ic, &sc->cmdq_task) in rum_vap_delete before the vap memory is freed (what the sim's mode 1 models). git apply --check clean. Kernel fix validation hardware-gated (fix_status: not_testable).

VERDICT.md
↓ download raw

DF-5293 VERDICT β€” deferred cmdq entry executes rum_set_beacon on a freed rum_vap

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

Reproduction path (in-kernel)

  1. Attach rum(4), create hostap/ibss wlan0, bring up (S_RUN; beacon exists).
  2. Trigger iv_update_beacon (TIM churn from a power-save client, ERP change, ioctl): rum_update_beacon queues &vap into cmdq[] and ieee80211_runtask(cmdq_task) (:2830, :768).
  3. Before the taskqueue thread executes cmdq_task (backlogged queue, or plain scheduling latency), destroy the vap: ifconfig wlan0 destroy β†’ rum_vap_delete β†’ kfree(rvp) (:720). Nothing drains sc->cmdq_task and nothing purges the entry.
  4. Taskqueue runs rum_update_beacon_cb β†’ rum_set_beacon(sc, vap): - :2733 m = rvp->bcn_mbuf β€” read of freed rum_vap (also the mbuf itself was freed at :715) - :2744 vap->iv_txparms[...] β€” read of freed rum_vap - :2754-2755 rum_write_multi(..., mtod(m), m->m_pkthdr.len) β€” wild kernel read with freed-heap-controlled length, streamed to the device. Freed rum_vap slots are immediately reusable (any vap create / other M_80211_VAP allocations), so the callback operates on reallocated memory β†’ garbage pointers β†’ panic-capable.

Why inconclusive on this guest

No RT2573 hardware; rum(4) never attaches; the cmdq/vap lifecycle cannot be entered. Structural evidence only (source md5-identical, module loads).

Simulated evidence

rum_cmdq_uaf.c models the 16-slot ring, producers storing the raw pointer, a taskqueue worker with dispatch latency, and the destroyer running rum_vap_delete with (mode 0) and without (mode 1) the missing drain. Guest results (3Γ—10 s each): shipped mode β†’ 11599/11849/12071 post-free rum_set_beacon executions (~61 % of callbacks); drained mode β†’ 0/0/0. The destroyer's free-window and the worker latency are modeled timings; the structural claim (no drain β‡’ queued entry can outlive the vap) is exact.

Divergences from the twins (checked, NOT affected)

  • cmdq ring locking: rum producers take RUM_CMDQ_LOCK (:753-766) β€” the torn-publish bug of DF-5249/DF-5262 does NOT apply.
  • key deferral: rum_key_set/rum_key_delete copy sizeof(*k) BY VALUE (:3021/:3038) β€” the raw-key UAF of DF-5250/DF-5263 does NOT apply.
  • The vap pointer at :2830 is the single dangling deferred reference.

Exploit chain (if hardware present)

post-free rum_vap reallocation (heap groom of M_80211_VAP zone) β†’ rum_set_beacon reads attacker-shaped bcn_mbuf β†’ rum_write_multi streams m_pkthdr.len bytes from an attacker-shaped pointer to the device (info disclosure over the air in hostap mode) plus wild kernel read β†’ panic; primarily a UAF-driven DoS/disclosure primitive.

Fix

ieee80211_draintask(ic, &sc->cmdq_task) in rum_vap_delete() before kfree(rvp) (fix.diff, git apply --check clean). Overlaps the DF-5292 fix in rum_vap_delete (trivial merge). Kernel fix validation is hardware-gated: not_testable.

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 behavior cannot be exercised without an RT2573 device (no USB passthrough/device model on the guest). The userland model demonstrates the fix protocol reduces post-free callback executions from ~61% to exactly 0. Hardware-gated: not_testable.

fix.diff (git-apply clean); run.log fixed-mode lines '=0/0/0'; VERDICT.md
↓ fix.diffper-fix-DF-5293

Confirmed kernel references

Detail

Exploit chain

beacon-update cmd queued (:2830) -> ifconfig destroy before the backlogged taskqueue runs cmdq_task -> kfree(rvp) (:720) -> rum_update_beacon_cb reads freed rum_vap: bcn_mbuf wild pointer + garbage m_pkthdr.len -> rum_write_multi streams len bytes from a freed-heap-shaped kernel address to the device (over-the-air disclosure in hostap mode) and wild-reads kernel memory -> panic-capable UAF primitive; M_80211_VAP slot reallocation gives the attacker shape control over the freed object

Evidence (decisive lines)

run.log: shipped mode 'POST-FREE rum_set_beacon=11599/11849/12071', fixed mode '=0/0/0'; rum_cmdq_uaf.c models the 16-slot ring, raw-pointer producer, dispatch-latency taskqueue and the undrained destroy; VERDICT.md lists the two twin-bugs that were checked and do NOT apply to rum

PoC changes

Harness written from scratch (no seed); pthread-based ring + condvar taskqueue with realistic dispatch latency; poison flag on the vap so post-free cb executions are counted rather than crashing; mode 1 adds the exact draintask-before-kfree the fix proposes

Verified recommended fix

ieee80211_draintask(ic, &sc->cmdq_task) in rum_vap_delete() before kfree(rvp), so queued beacon-update cmds referencing the vap complete first

Verdict

rum's instance of the DF-5247 (if_run) / DF-5260 (if_mtw) cmdq-lifetime twin. rum_update_beacon() queues the RAW vap pointer into the deferred command ring (if_rum.c:2830, rum_cmd_sleepable :745-771 only ieee80211_runtask()s it) and the taskqueue later runs rum_update_beacon_cb (:2784-2790) -> rum_set_beacon(sc, data->vap) (:2729) which reads rvp->bcn_mbuf (:2733), vap->iv_txparms (:2744) and streams mtod(m)/m_pkthdr.len bytes to the device (:2754-2755). rum_vap_delete() (:709-721) kfree()s the rum_vap (:720) after draining ONLY rvp->ratectl_task (:717) - sc->cmdq_task is never drained and the ring is never purged, so a queued callback executes on freed (and immediately reusable, M_80211_VAP-zone) memory. Divergences from the twins verified: rum's cmdq ring producers DO take RUM_CMDQ_LOCK (:753-766), so the DF-5249/5262 torn-publish bug does not apply; rum_key_set/rum_key_delete copy the key BY VALUE (:3021/:3038), so the DF-5250/5263 raw-key UAF does not apply - the vap pointer at :2830 is the single dangling deferred reference. In-kernel reproduction is hardware-gated (no RT2573 on the QEMU guest). Simulated on the guest with a line-faithful ring+taskqueue+destroyer model: shipped code produced 11599/11849/12071 post-free rum_set_beacon executions across 3x10s runs (~61% of all callbacks), while adding the missing draintask before kfree yields exactly 0/0/0.