wifi: mt76: fix scan work requeue race with spinlock

Prevent mt76_scan_rx_beacon() in softirq from requeuing scan_work
after cancel_delayed_work_sync() returns in mt76_abort_scan(). Without
synchronization, the beacon handler can observe beacon_wait as true
and requeue scan_work between the cancel and the memset in
mt76_scan_complete(), leading to a NULL pointer dereference when
scan_work runs against zeroed scan state.

Introduce scan_lock (placed outside the scan struct to survive the
memset) to make the beacon handler's check-and-queue atomic with
respect to abort_scan's clear of beacon_wait. This eliminates the
race with a single cancel_delayed_work_sync() call.

Signed-off-by: Felix Fietkau <[email protected]>
This commit is contained in:
Felix Fietkau
2026-03-09 05:11:31 +00:00
parent fb6a584e71
commit ab6abc9713
3 changed files with 17 additions and 2 deletions
+1
View File
@@ -728,6 +728,7 @@ mt76_alloc_device(struct device *pdev, unsigned int size,
INIT_LIST_HEAD(&dev->rxwi_cache); INIT_LIST_HEAD(&dev->rxwi_cache);
dev->token_size = dev->drv->token_size; dev->token_size = dev->drv->token_size;
INIT_DELAYED_WORK(&dev->scan_work, mt76_scan_work); INIT_DELAYED_WORK(&dev->scan_work, mt76_scan_work);
spin_lock_init(&dev->scan_lock);
for (i = 0; i < ARRAY_SIZE(dev->q_rx); i++) for (i = 0; i < ARRAY_SIZE(dev->q_rx); i++)
skb_queue_head_init(&dev->rx_skb[i]); skb_queue_head_init(&dev->rx_skb[i]);
+1
View File
@@ -1021,6 +1021,7 @@ struct mt76_dev {
u32 rxfilter; u32 rxfilter;
struct delayed_work scan_work; struct delayed_work scan_work;
spinlock_t scan_lock;
struct { struct {
struct cfg80211_scan_request *req; struct cfg80211_scan_request *req;
struct ieee80211_channel *chan; struct ieee80211_channel *chan;
+15 -2
View File
@@ -29,6 +29,10 @@ static void mt76_scan_complete(struct mt76_dev *dev, bool abort)
void mt76_abort_scan(struct mt76_dev *dev) void mt76_abort_scan(struct mt76_dev *dev)
{ {
spin_lock_bh(&dev->scan_lock);
dev->scan.beacon_wait = false;
spin_unlock_bh(&dev->scan_lock);
cancel_delayed_work_sync(&dev->scan_work); cancel_delayed_work_sync(&dev->scan_work);
mt76_scan_complete(dev, true); mt76_scan_complete(dev, true);
} }
@@ -85,16 +89,21 @@ void mt76_scan_rx_beacon(struct mt76_dev *dev, struct ieee80211_channel *chan)
{ {
struct mt76_phy *phy; struct mt76_phy *phy;
spin_lock(&dev->scan_lock);
if (!dev->scan.beacon_wait || dev->scan.beacon_received || if (!dev->scan.beacon_wait || dev->scan.beacon_received ||
dev->scan.chan != chan) dev->scan.chan != chan)
return; goto out;
phy = dev->scan.phy; phy = dev->scan.phy;
if (!phy) if (!phy)
return; goto out;
dev->scan.beacon_received = true; dev->scan.beacon_received = true;
ieee80211_queue_delayed_work(phy->hw, &dev->scan_work, 0); ieee80211_queue_delayed_work(phy->hw, &dev->scan_work, 0);
out:
spin_unlock(&dev->scan_lock);
} }
void mt76_scan_work(struct work_struct *work) void mt76_scan_work(struct work_struct *work)
@@ -111,8 +120,10 @@ void mt76_scan_work(struct work_struct *work)
if (!phy || !req) if (!phy || !req)
return; return;
spin_lock_bh(&dev->scan_lock);
beacon_rx = dev->scan.beacon_wait && dev->scan.beacon_received; beacon_rx = dev->scan.beacon_wait && dev->scan.beacon_received;
dev->scan.beacon_wait = false; dev->scan.beacon_wait = false;
spin_unlock_bh(&dev->scan_lock);
if (beacon_rx) if (beacon_rx)
goto probe; goto probe;
@@ -140,8 +151,10 @@ void mt76_scan_work(struct work_struct *work)
goto out; goto out;
if (chandef.chan->flags & (IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_RADAR)) { if (chandef.chan->flags & (IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_RADAR)) {
spin_lock_bh(&dev->scan_lock);
dev->scan.beacon_received = false; dev->scan.beacon_received = false;
dev->scan.beacon_wait = true; dev->scan.beacon_wait = true;
spin_unlock_bh(&dev->scan_lock);
goto out; goto out;
} }