mac80211: fix scan vs. interface removal race

When we remove an interface, we can currently end up having
a pointer to it left in local->scan_sdata after it has been
set down, and then with a hardware scan the scan completion
can try to access it which is a bug. Alternatively, a scan
that started as a hardware scan may terminate as though it
was a software scan, if the timing is just right.

On SMP systems, software scan also has a similar problem,
just canceling the delayed work and setting a flag isn't
enough since it may be running concurrently; in this case
we would also never restore state of other interfaces.

This patch hopefully fixes the problems by always invoking
ieee80211_scan_completed or requiring it to be invoked by
the driver, I suspect the drivers that have ->hw_scan() are
buggy. The bug will not manifest itself unless you remove
the interface while hw-scanning which will also turn off
the hw, and then add a new interface which will be unusable
until you scan once.

Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
This commit is contained in:
Johannes Berg
2008-09-11 00:01:51 +02:00
committed by John W. Linville
parent b7413430d4
commit 5bc75728fd
4 changed files with 56 additions and 21 deletions

View File

@@ -564,14 +564,6 @@ static int ieee80211_stop(struct net_device *dev)
synchronize_rcu();
skb_queue_purge(&sdata->u.sta.skb_queue);
if (local->scan_sdata == sdata) {
if (!local->ops->hw_scan) {
local->sta_sw_scanning = 0;
cancel_delayed_work(&local->scan_work);
} else
local->sta_hw_scanning = 0;
}
sdata->u.sta.flags &= ~IEEE80211_STA_PRIVACY_INVOKED;
kfree(sdata->u.sta.extra_ie);
sdata->u.sta.extra_ie = NULL;
@@ -585,6 +577,31 @@ static int ieee80211_stop(struct net_device *dev)
}
/* fall through */
default:
if (local->scan_sdata == sdata) {
if (!local->ops->hw_scan)
cancel_delayed_work_sync(&local->scan_work);
/*
* The software scan can no longer run now, so we can
* clear out the scan_sdata reference. However, the
* hardware scan may still be running. The complete
* function must be prepared to handle a NULL value.
*/
local->scan_sdata = NULL;
/*
* The memory barrier guarantees that another CPU
* that is hardware-scanning will now see the fact
* that this interface is gone.
*/
smp_mb();
/*
* If software scanning, complete the scan but since
* the scan_sdata is NULL already don't send out a
* scan event to userspace -- the scan is incomplete.
*/
if (local->sta_sw_scanning)
ieee80211_scan_completed(&local->hw);
}
conf.vif = &sdata->vif;
conf.type = sdata->vif.type;
conf.mac_addr = dev->dev_addr;