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

rt2560 beacon-ring slot overwrite leaks the old beacon mbuf and its ieee80211_node reference on every re-entry to RUN β€” remotely pump-able in IBSS mode (ibss merge)

Summary

The beacon ring is a single slot with no completion handler; the only reclaim paths are stop/reset. Every transition to S_RUN allocates a fresh beacon and hands it to rt2560_tx_bcn, which stores m/ni into the slot without freeing the previous occupants. Re-RUN without a stop is a normal event: ibss_merge -> sta_join1 -> new_state(S_RUN) for non-STA opmodes - driven by any on-channel peer beacon with a greater TSF - and ic_nrunning never drops, so the ring is never stopped. Each merge leaks one ~2KB mbuf cluster plus one node reference; the tx_bcn error path also leaks the ref. Multi-slot aliasing variant killed (COUNT==1 -> no double ownership). Free-stale-occupants fix (module rebuilt -Werror clean).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3971 Β· 11 files
FileTypeDescriptionSize
README.md β€” 1.9 KB ↓ raw
VERDICT.md β€” 2.4 KB ↓ raw
trigger.c β€” 2.3 KB view raw
build.sh β€” 67 B view raw
run.sh β€” 91 B view raw
run.log β€” 317 B view raw
env.txt β€” 317 B view raw
fix.diff β€” 955 B view raw
build.log β€” 37.9 KB view raw
manifest.json β€” 620 B view raw
verdict.json β€” 3.6 KB view raw

DF-3971 β€” rt2560 beacon-ring slot overwrite leaks the old beacon mbuf + node reference on every re-RUN transition (remotely pump-able in IBSS)

File: sys/dev/netif/ral/rt2560.c (pass 2, GLM 5.3)

What

RT2560_BEACON_RING_COUNT is 1 (rt2560reg.h:26) β€” one beacon slot. Each entry into IEEE80211_S_RUN (rt2560.c:811-824) allocates a fresh beacon mbuf, takes a node reference, and hands both to rt2560_tx_bcn, which stores them into the slot (1546-1547) without freeing whatever the slot still holds. Whenever RUN is re-entered while the interface stays up, the previous m and ni pointers are overwritten and leaked (one ~2KB mbuf cluster + one ieee80211_node ref, pinning the node table entry).

Re-RUN without a stop is a normal, remotely-drivable event in IBSS: any peer beacon with same SSID/channel and higher TSF forces a merge β€” ieee80211_node.c:610-637 (ieee80211_ibss_merge) β†’ ieee80211_sta_join1 (ieee80211_node.c:427+) β†’ ieee80211_new_state(vap, IEEE80211_S_RUN, -1) for non-STA opmodes. Each merge leaks. Additionally, when rt2560_tx_bcn fails (map load error β€” it frees the m itself), the ieee80211_ref_node taken at rt2560.c:820 is never dropped.

Impact

Unbounded kernel memory growth (mbuf clusters + pinned nodes) from an on-channel remote peer, after an admin has configured adhoc/hostap mode. No memory corruption. Low.

Build / Run

Hardware-gated (RT2560 NIC; guest has none β€” env.txt): cc trigger.c (informational) # on target: adhoc vap up; from a 2nd radio rotate adhoc BSSIDs with # increasing TSF; watch netstat -m / node count climb per merge.

Fix validation

fix.diff frees the slot's stale occupants in rt2560_tx_bcn (guarded by data->m != m0 / data->ni != ni so the beacon_expire reuse path β€” which re-loads the same m β€” is untouched) and drops the leaked ref on the tx_bcn error path in newstate. git apply --check clean; module rebuilt clean with -Werror on the guest (build.log).

VERDICT.md
↓ download raw

DF-3971 verdict β€” certain lifetime leak (mbuf + node ref) per re-RUN; remotely pump-able in IBSS

Reproduced?

No Ralink NIC on the audit guest (env.txt) β€” the leak could not be observed at runtime. The source chain is complete and every link was traced:

  • rt2560reg.h:26: RT2560_BEACON_RING_COUNT 1 β€” single beacon slot; there is no completion handler for bcnq, so the only paths that clear slot 0 are reset_tx_ring (stop) and free_tx_ring (detach).
  • rt2560.c:811-824 (newstate RUN): ieee80211_beacon_alloc + ref_node + rt2560_tx_bcn β€” on re-entry to RUN the slot is not checked or cleared.
  • rt2560.c:1546-1547 (tx_bcn): data->m = m0; data->ni = ni; unconditional overwrite of slot 0's previous occupants β†’ leak of old mbuf + old node ref.
  • Re-RUN-without-stop is real: ieee80211_node.c:610-637 ibss_merge β†’ ieee80211_node.c:427+ sta_join1 β†’ ieee80211_new_state(vap, IEEE80211_S_RUN, -1) for non-STA opmodes β€” driven by any on-channel peer beacon with a greater TSF (the "oldest network wins" merge rule), so a remote party can cycle BSSIDs and leak once per merge. ic_nrunning never drops to 0 in this flow, so rt2560_parent never issues the stop/reset that would reclaim the slot.
  • Error path: rt2560.c:820-823 β€” if tx_bcn fails (it m_freem()s the m itself at 1529-1534), the ref taken at 820 is never dropped.

Note what this is NOT: with RING_COUNT==1 there is no double-free / aliasing (beacon_expire and tx_bcn share the same slot 0 and the same m), unlike the multi-slot variant I also analyzed and killed. Pure resource leak, no memory corruption β€” the pinned node keeps a valid bss referenced, so no UAF either.

Fix

fix.diff: (1) in rt2560_tx_bcn, unload+free a stale slot occupant before storing, guarded by data->m != m0 / data->ni != ni so the beacon_expire reload path (rt2560.c:1355-1368) which passes the same m/ni is unaffected; (2) drop the leaked ref when tx_bcn errors in newstate. git apply --check clean on sys/; applied on the guest /usr/src copy, if_ral.ko rebuilt clean with -Werror (build.log). Runtime validation needs the NIC.

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

Stale-slot cleanup (guarded by data->m != m0 / data->ni != ni so the beacon_expire reload of the same mbuf is untouched) + error-path ieee80211_free_node(ni); git apply --check clean against sys/ and the patched module rebuilt -Werror clean (build.log; combined with the other two rt2560 fix.diffs of this batch). Runtime validation impossible without the NIC.

fix.diff; build.log (if_ral.ko linked from patched rt2560.o with -Werror on the guest /usr/src copy)
↓ fix.diffper-fix-DF-3971

Confirmed kernel references

Detail

Exploit chain

admin brings up ral0 adhoc vap -> remote peer rotates adhoc BSSIDs with increasing TSF on same channel/SSID -> each ibss merge re-enters S_RUN while ic_nrunning stays >0 (no stop/reset) -> rt2560_tx_bcn overwrites slot 0 -> old beacon mbuf + node ref leaked -> unbounded kernel memory growth (netstat -m climb).

Evidence (decisive lines)

VERDICT.md (full leak chain with path:line, incl. the killed double-free variant), trigger.c (on-target recipe), env.txt, fix.diff (stale-slot cleanup guarded by data->m != m0 / data->ni != ni + error-path ref drop), build.log (if_ral.ko rebuilt -Werror clean on the guest /usr/src copy)

PoC changes

Trigger is a documented IBSS-merge pump recipe (needs two radios); informational C stub compiled only.

Verified recommended fix

Free stale beacon-slot occupants in rt2560_tx_bcn before storing (guarded against the beacon_expire same-mbuf reload) and drop the leaked ref on the tx_bcn error path (fix.diff).

Verdict

Certain resource leak: rt2560_tx_bcn (rt2560.c:1546-1547) unconditionally overwrites the single beacon-ring slot (RT2560_BEACON_RING_COUNT==1, rt2560reg.h:26) without freeing its previous mbuf/ni occupants, and rt2560_newstate (rt2560.c:811-824) re-enters this path on every transition to IEEE80211_S_RUN. Re-RUN without an interface stop is a normal remotely-drivable event in IBSS mode: any on-channel peer beacon with greater TSF forces ieee80211_ibss_merge (ieee80211_node.c:610-637) -> ieee80211_sta_join1 -> ieee80211_new_state(vap, S_RUN, -1) for non-STA opmodes, leaking ~2KB of mbuf cluster plus one ieee80211_node reference per merge; the tx_bcn error path additionally leaks the ref_node taken at rt2560.c:820. Pure leak (with RING_COUNT==1 there is no slot aliasing/double-free β€” that variant was analyzed and killed); unbounded kernel memory growth, no corruption. Not reproduced: no Ralink NIC on the guest (env.txt); IBSS-merge leak pump recipe in trigger.c.