mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-21 16:29:59 +00:00
cfg80211: check for and abort dangling scan requests
If you trigger a scan request on an interface and then take it down, or rmmod the module or unplug the device the driver might "forget" to cancel the scan request. That is a bug in the driver, but the current behaviour is that we just hang endlessly waiting for the netdev refcount to become 0 which it never will. To improve robustness, check for this situation in cfg80211, warn about it and clean up behind the driver. I don't just clean up silently because it's likely that the driver also has some internal state it has now leaked. Additionally, this fixes a locking bug, clearing the scan_req pointer should be done under the rdev lock. Finally, we also need to _wait_ for the scan work and not just abort it since it might be pending and wanting to do a cleanup. Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: John W. Linville <linville@tuxdriver.com>
This commit is contained in:
parent
70bdb6b275
commit
36e6fea849
3 changed files with 26 additions and 11 deletions
|
@ -601,8 +601,8 @@ void wiphy_unregister(struct wiphy *wiphy)
|
||||||
|
|
||||||
mutex_unlock(&cfg80211_mutex);
|
mutex_unlock(&cfg80211_mutex);
|
||||||
|
|
||||||
|
flush_work(&rdev->scan_done_wk);
|
||||||
cancel_work_sync(&rdev->conn_work);
|
cancel_work_sync(&rdev->conn_work);
|
||||||
cancel_work_sync(&rdev->scan_done_wk);
|
|
||||||
kfree(rdev->scan_req);
|
kfree(rdev->scan_req);
|
||||||
flush_work(&rdev->event_work);
|
flush_work(&rdev->event_work);
|
||||||
}
|
}
|
||||||
|
@ -728,6 +728,13 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb,
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
case NETDEV_UNREGISTER:
|
case NETDEV_UNREGISTER:
|
||||||
|
cfg80211_lock_rdev(rdev);
|
||||||
|
|
||||||
|
if (WARN_ON(rdev->scan_req && rdev->scan_req->dev == dev)) {
|
||||||
|
rdev->scan_req->aborted = true;
|
||||||
|
___cfg80211_scan_done(rdev);
|
||||||
|
}
|
||||||
|
|
||||||
mutex_lock(&rdev->devlist_mtx);
|
mutex_lock(&rdev->devlist_mtx);
|
||||||
/*
|
/*
|
||||||
* It is possible to get NETDEV_UNREGISTER
|
* It is possible to get NETDEV_UNREGISTER
|
||||||
|
@ -746,6 +753,7 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb,
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
mutex_unlock(&rdev->devlist_mtx);
|
mutex_unlock(&rdev->devlist_mtx);
|
||||||
|
cfg80211_unlock_rdev(rdev);
|
||||||
break;
|
break;
|
||||||
case NETDEV_PRE_UP:
|
case NETDEV_PRE_UP:
|
||||||
if (!(wdev->wiphy->interface_modes & BIT(wdev->iftype)))
|
if (!(wdev->wiphy->interface_modes & BIT(wdev->iftype)))
|
||||||
|
|
|
@ -368,6 +368,7 @@ void cfg80211_sme_scan_done(struct net_device *dev);
|
||||||
void cfg80211_sme_rx_auth(struct net_device *dev, const u8 *buf, size_t len);
|
void cfg80211_sme_rx_auth(struct net_device *dev, const u8 *buf, size_t len);
|
||||||
void cfg80211_sme_disassoc(struct net_device *dev, int idx);
|
void cfg80211_sme_disassoc(struct net_device *dev, int idx);
|
||||||
void __cfg80211_scan_done(struct work_struct *wk);
|
void __cfg80211_scan_done(struct work_struct *wk);
|
||||||
|
void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev);
|
||||||
void cfg80211_upload_connect_keys(struct wireless_dev *wdev);
|
void cfg80211_upload_connect_keys(struct wireless_dev *wdev);
|
||||||
|
|
||||||
struct ieee80211_channel *
|
struct ieee80211_channel *
|
||||||
|
|
|
@ -18,19 +18,14 @@
|
||||||
|
|
||||||
#define IEEE80211_SCAN_RESULT_EXPIRE (15 * HZ)
|
#define IEEE80211_SCAN_RESULT_EXPIRE (15 * HZ)
|
||||||
|
|
||||||
void __cfg80211_scan_done(struct work_struct *wk)
|
void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev)
|
||||||
{
|
{
|
||||||
struct cfg80211_registered_device *rdev;
|
|
||||||
struct cfg80211_scan_request *request;
|
struct cfg80211_scan_request *request;
|
||||||
struct net_device *dev;
|
struct net_device *dev;
|
||||||
#ifdef CONFIG_WIRELESS_EXT
|
#ifdef CONFIG_WIRELESS_EXT
|
||||||
union iwreq_data wrqu;
|
union iwreq_data wrqu;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
rdev = container_of(wk, struct cfg80211_registered_device,
|
|
||||||
scan_done_wk);
|
|
||||||
|
|
||||||
mutex_lock(&rdev->mtx);
|
|
||||||
request = rdev->scan_req;
|
request = rdev->scan_req;
|
||||||
|
|
||||||
dev = request->dev;
|
dev = request->dev;
|
||||||
|
@ -43,9 +38,9 @@ void __cfg80211_scan_done(struct work_struct *wk)
|
||||||
cfg80211_sme_scan_done(dev);
|
cfg80211_sme_scan_done(dev);
|
||||||
|
|
||||||
if (request->aborted)
|
if (request->aborted)
|
||||||
nl80211_send_scan_aborted(wiphy_to_dev(request->wiphy), dev);
|
nl80211_send_scan_aborted(rdev, dev);
|
||||||
else
|
else
|
||||||
nl80211_send_scan_done(wiphy_to_dev(request->wiphy), dev);
|
nl80211_send_scan_done(rdev, dev);
|
||||||
|
|
||||||
#ifdef CONFIG_WIRELESS_EXT
|
#ifdef CONFIG_WIRELESS_EXT
|
||||||
if (!request->aborted) {
|
if (!request->aborted) {
|
||||||
|
@ -57,11 +52,22 @@ void __cfg80211_scan_done(struct work_struct *wk)
|
||||||
|
|
||||||
dev_put(dev);
|
dev_put(dev);
|
||||||
|
|
||||||
cfg80211_unlock_rdev(rdev);
|
rdev->scan_req = NULL;
|
||||||
wiphy_to_dev(request->wiphy)->scan_req = NULL;
|
|
||||||
kfree(request);
|
kfree(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void __cfg80211_scan_done(struct work_struct *wk)
|
||||||
|
{
|
||||||
|
struct cfg80211_registered_device *rdev;
|
||||||
|
|
||||||
|
rdev = container_of(wk, struct cfg80211_registered_device,
|
||||||
|
scan_done_wk);
|
||||||
|
|
||||||
|
cfg80211_lock_rdev(rdev);
|
||||||
|
___cfg80211_scan_done(rdev);
|
||||||
|
cfg80211_unlock_rdev(rdev);
|
||||||
|
}
|
||||||
|
|
||||||
void cfg80211_scan_done(struct cfg80211_scan_request *request, bool aborted)
|
void cfg80211_scan_done(struct cfg80211_scan_request *request, bool aborted)
|
||||||
{
|
{
|
||||||
WARN_ON(request != wiphy_to_dev(request->wiphy)->scan_req);
|
WARN_ON(request != wiphy_to_dev(request->wiphy)->scan_req);
|
||||||
|
|
Loading…
Reference in a new issue