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)
PoC verification
Evidence pack
findings/poc/DF-3971 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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).
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_testableStale-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)
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.
No comments yet.