diff options
author | Johannes Berg <johannes@sipsolutions.net> | 2008-09-10 18:01:51 -0400 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2008-09-15 16:48:20 -0400 |
commit | 5bc75728fd43bb15b46f16ef465bcf9d487393cf (patch) | |
tree | 5732adee3965970390bf7953d214c757bbdba2a2 /net/mac80211 | |
parent | b7413430d4d2a6168e68231d9f93763047b6d60c (diff) |
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>
Diffstat (limited to 'net/mac80211')
-rw-r--r-- | net/mac80211/main.c | 33 | ||||
-rw-r--r-- | net/mac80211/mlme.c | 2 | ||||
-rw-r--r-- | net/mac80211/scan.c | 38 |
3 files changed, 53 insertions, 20 deletions
diff --git a/net/mac80211/main.c b/net/mac80211/main.c index ebdec7106d63..4bfac4b41c51 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c | |||
@@ -564,14 +564,6 @@ static int ieee80211_stop(struct net_device *dev) | |||
564 | synchronize_rcu(); | 564 | synchronize_rcu(); |
565 | skb_queue_purge(&sdata->u.sta.skb_queue); | 565 | skb_queue_purge(&sdata->u.sta.skb_queue); |
566 | 566 | ||
567 | if (local->scan_sdata == sdata) { | ||
568 | if (!local->ops->hw_scan) { | ||
569 | local->sta_sw_scanning = 0; | ||
570 | cancel_delayed_work(&local->scan_work); | ||
571 | } else | ||
572 | local->sta_hw_scanning = 0; | ||
573 | } | ||
574 | |||
575 | sdata->u.sta.flags &= ~IEEE80211_STA_PRIVACY_INVOKED; | 567 | sdata->u.sta.flags &= ~IEEE80211_STA_PRIVACY_INVOKED; |
576 | kfree(sdata->u.sta.extra_ie); | 568 | kfree(sdata->u.sta.extra_ie); |
577 | sdata->u.sta.extra_ie = NULL; | 569 | sdata->u.sta.extra_ie = NULL; |
@@ -585,6 +577,31 @@ static int ieee80211_stop(struct net_device *dev) | |||
585 | } | 577 | } |
586 | /* fall through */ | 578 | /* fall through */ |
587 | default: | 579 | default: |
580 | if (local->scan_sdata == sdata) { | ||
581 | if (!local->ops->hw_scan) | ||
582 | cancel_delayed_work_sync(&local->scan_work); | ||
583 | /* | ||
584 | * The software scan can no longer run now, so we can | ||
585 | * clear out the scan_sdata reference. However, the | ||
586 | * hardware scan may still be running. The complete | ||
587 | * function must be prepared to handle a NULL value. | ||
588 | */ | ||
589 | local->scan_sdata = NULL; | ||
590 | /* | ||
591 | * The memory barrier guarantees that another CPU | ||
592 | * that is hardware-scanning will now see the fact | ||
593 | * that this interface is gone. | ||
594 | */ | ||
595 | smp_mb(); | ||
596 | /* | ||
597 | * If software scanning, complete the scan but since | ||
598 | * the scan_sdata is NULL already don't send out a | ||
599 | * scan event to userspace -- the scan is incomplete. | ||
600 | */ | ||
601 | if (local->sta_sw_scanning) | ||
602 | ieee80211_scan_completed(&local->hw); | ||
603 | } | ||
604 | |||
588 | conf.vif = &sdata->vif; | 605 | conf.vif = &sdata->vif; |
589 | conf.type = sdata->vif.type; | 606 | conf.type = sdata->vif.type; |
590 | conf.mac_addr = dev->dev_addr; | 607 | conf.mac_addr = dev->dev_addr; |
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 9e20a0c20a46..19c7f21e49d1 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c | |||
@@ -2530,7 +2530,7 @@ void ieee80211_mlme_notify_scan_completed(struct ieee80211_local *local) | |||
2530 | struct ieee80211_sub_if_data *sdata = local->scan_sdata; | 2530 | struct ieee80211_sub_if_data *sdata = local->scan_sdata; |
2531 | struct ieee80211_if_sta *ifsta; | 2531 | struct ieee80211_if_sta *ifsta; |
2532 | 2532 | ||
2533 | if (sdata->vif.type == IEEE80211_IF_TYPE_IBSS) { | 2533 | if (sdata && sdata->vif.type == IEEE80211_IF_TYPE_IBSS) { |
2534 | ifsta = &sdata->u.sta; | 2534 | ifsta = &sdata->u.sta; |
2535 | if (!(ifsta->flags & IEEE80211_STA_BSSID_SET) || | 2535 | if (!(ifsta->flags & IEEE80211_STA_BSSID_SET) || |
2536 | (!(ifsta->state == IEEE80211_STA_MLME_IBSS_JOINED) && | 2536 | (!(ifsta->state == IEEE80211_STA_MLME_IBSS_JOINED) && |
diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index f4399e9ac928..27727027d76d 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c | |||
@@ -430,9 +430,20 @@ void ieee80211_scan_completed(struct ieee80211_hw *hw) | |||
430 | struct ieee80211_sub_if_data *sdata; | 430 | struct ieee80211_sub_if_data *sdata; |
431 | union iwreq_data wrqu; | 431 | union iwreq_data wrqu; |
432 | 432 | ||
433 | if (WARN_ON(!local->sta_hw_scanning && !local->sta_sw_scanning)) | ||
434 | return; | ||
435 | |||
433 | local->last_scan_completed = jiffies; | 436 | local->last_scan_completed = jiffies; |
434 | memset(&wrqu, 0, sizeof(wrqu)); | 437 | memset(&wrqu, 0, sizeof(wrqu)); |
435 | wireless_send_event(local->scan_sdata->dev, SIOCGIWSCAN, &wrqu, NULL); | 438 | |
439 | /* | ||
440 | * local->scan_sdata could have been NULLed by the interface | ||
441 | * down code in case we were scanning on an interface that is | ||
442 | * being taken down. | ||
443 | */ | ||
444 | sdata = local->scan_sdata; | ||
445 | if (sdata) | ||
446 | wireless_send_event(sdata->dev, SIOCGIWSCAN, &wrqu, NULL); | ||
436 | 447 | ||
437 | if (local->sta_hw_scanning) { | 448 | if (local->sta_hw_scanning) { |
438 | local->sta_hw_scanning = 0; | 449 | local->sta_hw_scanning = 0; |
@@ -491,7 +502,10 @@ void ieee80211_sta_scan_work(struct work_struct *work) | |||
491 | int skip; | 502 | int skip; |
492 | unsigned long next_delay = 0; | 503 | unsigned long next_delay = 0; |
493 | 504 | ||
494 | if (!local->sta_sw_scanning) | 505 | /* |
506 | * Avoid re-scheduling when the sdata is going away. | ||
507 | */ | ||
508 | if (!netif_running(sdata->dev)) | ||
495 | return; | 509 | return; |
496 | 510 | ||
497 | switch (local->scan_state) { | 511 | switch (local->scan_state) { |
@@ -570,9 +584,8 @@ void ieee80211_sta_scan_work(struct work_struct *work) | |||
570 | break; | 584 | break; |
571 | } | 585 | } |
572 | 586 | ||
573 | if (local->sta_sw_scanning) | 587 | queue_delayed_work(local->hw.workqueue, &local->scan_work, |
574 | queue_delayed_work(local->hw.workqueue, &local->scan_work, | 588 | next_delay); |
575 | next_delay); | ||
576 | } | 589 | } |
577 | 590 | ||
578 | 591 | ||
@@ -609,13 +622,16 @@ int ieee80211_sta_start_scan(struct ieee80211_sub_if_data *scan_sdata, | |||
609 | } | 622 | } |
610 | 623 | ||
611 | if (local->ops->hw_scan) { | 624 | if (local->ops->hw_scan) { |
612 | int rc = local->ops->hw_scan(local_to_hw(local), | 625 | int rc; |
613 | ssid, ssid_len); | 626 | |
614 | if (!rc) { | 627 | local->sta_hw_scanning = 1; |
615 | local->sta_hw_scanning = 1; | 628 | rc = local->ops->hw_scan(local_to_hw(local), ssid, ssid_len); |
616 | local->scan_sdata = scan_sdata; | 629 | if (rc) { |
630 | local->sta_hw_scanning = 0; | ||
631 | return rc; | ||
617 | } | 632 | } |
618 | return rc; | 633 | local->scan_sdata = scan_sdata; |
634 | return 0; | ||
619 | } | 635 | } |
620 | 636 | ||
621 | local->sta_sw_scanning = 1; | 637 | local->sta_sw_scanning = 1; |