This repository has no description
4.3 kB
97 lines
1wifi: mt76: fix wcid poll list corruption on station removal
2
3Three system freezes on 2026-07-03 (ignavia, Framework 13 AMD, MT7925),
4two with captured oopses, both identical, on kernels 7.0.11 and 7.1.2:
5
6 wlp192s0: send auth to 80:2a:a8:52:b0:2f (try 1/3)
7 slab kmalloc-rnd-11-8k start ... pointer offset 4160 size 8192
8 list_add corruption. prev->next should be next (X), but was Y. (prev=Y)
9 kernel BUG at lib/list_debug.c:32!
10
11prev == prev->next means the tail node of sta_poll_list was
12re-initialized (INIT_LIST_HEAD) while still linked. The 8k slab is the
13ieee80211_sta + drv_priv allocation holding the embedded mt76_wcid.
14
15Race (mt7925, frequent roaming between APs of the same ESS):
16
17 CPU0 (rx/txs softirq) CPU1 (mac80211 sta teardown)
18 wcid = rcu_dereference(...)
19 rcu_assign_pointer(wcid[idx], NULL)
20 mt76_wcid_cleanup()
21 list_del_init(&wcid->poll_list)
22 mt76_wcid_add_poll(wcid)
23 wcid->sta still 1 -> re-adds
24 dying wcid to sta_poll_list
25 sta freed after RCU grace period
26 ... sta_poll_list tail dangles into freed slab ...
27 roam re-add reuses slab slot:
28 mt76_wcid_init()
29 INIT_LIST_HEAD(&wcid->poll_list)
30 -> still-linked tail self-loops
31 next mt76_wcid_add_poll()
32 list_add_tail() -> BUG
33
34mt76_reset_device() and the mt7915/mt7996 remove paths already clear
35wcid->sta (and set sta_disabled) at teardown; the mt792x remove paths
36never do, so the !wcid->sta guard in mt76_wcid_add_poll() is
37ineffective there, and it runs before taking sta_poll_lock anyway.
38
39Fix:
40 - mt76_wcid_cleanup(): mark the wcid dead (sta = 0, sta_disabled = 1)
41 under sta_poll_lock, in the same critical section that unlinks
42 poll_list.
43 - mt76_wcid_add_poll(): re-check wcid->sta under sta_poll_lock so the
44 check cannot race with cleanup.
45 - mt7925_mac_sta_poll(): take list_first_entry under sta_poll_lock
46 (same discipline mt7921 uses); the unlocked read can race with
47 cleanup unlinking the entry from the spliced private list.
48
49diff -ruN a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
50--- a/drivers/net/wireless/mediatek/mt76/mac80211.c 2026-06-27 12:08:18.000000000 +0200
51+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c 2026-07-03 16:04:01.869430650 +0200
52@@ -1727,6 +1727,13 @@
53 * when mt76_wcid_add_poll() checks list_empty().
54 */
55 spin_lock_bh(&dev->sta_poll_lock);
56+ /* Mark the wcid dead in the same critical section, so a concurrent
57+ * mt76_wcid_add_poll() from the rx/txs path cannot re-add it after
58+ * we unlink it. Freed wcids left on sta_poll_list corrupt the list
59+ * once the slab slot is reused and re-initialized.
60+ */
61+ wcid->sta = 0;
62+ wcid->sta_disabled = 1;
63 if (!list_empty(&wcid->poll_list))
64 list_del_init(&wcid->poll_list);
65 spin_unlock_bh(&dev->sta_poll_lock);
66@@ -1759,7 +1766,10 @@
67 return;
68
69 spin_lock_bh(&dev->sta_poll_lock);
70- if (list_empty(&wcid->poll_list))
71+ /* Re-check under the lock: mt76_wcid_cleanup() clears wcid->sta in
72+ * its sta_poll_lock section, so this cannot race with teardown.
73+ */
74+ if (wcid->sta && list_empty(&wcid->poll_list))
75 list_add_tail(&wcid->poll_list, &dev->sta_poll_list);
76 spin_unlock_bh(&dev->sta_poll_lock);
77 }
78diff -ruN a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
79--- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c 2026-06-27 12:08:18.000000000 +0200
80+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c 2026-07-03 16:04:01.869762977 +0200
81@@ -46,12 +46,14 @@
82 u16 idx;
83 u8 bw;
84
85- if (list_empty(&sta_poll_list))
86+ spin_lock_bh(&dev->mt76.sta_poll_lock);
87+ if (list_empty(&sta_poll_list)) {
88+ spin_unlock_bh(&dev->mt76.sta_poll_lock);
89 break;
90+ }
91 mlink = list_first_entry(&sta_poll_list,
92 struct mt792x_link_sta, wcid.poll_list);
93 msta = mlink->sta;
94- spin_lock_bh(&dev->mt76.sta_poll_lock);
95 list_del_init(&mlink->wcid.poll_list);
96 spin_unlock_bh(&dev->mt76.sta_poll_lock);
97