diff options
author | Eliad Peller <eliad@wizery.com> | 2013-12-05 11:30:17 -0500 |
---|---|---|
committer | Johannes Berg <johannes.berg@intel.com> | 2013-12-05 13:06:47 -0500 |
commit | 4a58e7c38443154fce1b47910e1a9184f65c5d72 (patch) | |
tree | 312b02efaf0de0522cca0eebfac2b5b861430d68 | |
parent | a2b70e833e189a4aefb2d3b668e3d7046dcc45c2 (diff) |
cfg80211: don't "leak" uncompleted scans
___cfg80211_scan_done() can be called in some cases
(e.g. on NETDEV_DOWN) before the low level driver
notified scan completion (which is indicated by
passing leak=true).
Clearing rdev->scan_req in this case is buggy, as
scan_done_wk might have already being queued/running
(and can't be flushed as it takes rtnl()).
If a new scan will be requested at this stage, the
scan_done_wk will try freeing it (instead of the
previous scan), and this will later result in
a use after free.
Simply remove the "leak" option, and replace it with
a standard WARN_ON.
An example backtrace after such crash:
Unable to handle kernel paging request at virtual address fffffee5
pgd = c0004000
[fffffee5] *pgd=9fdf6821, *pte=00000000, *ppte=00000000
Internal error: Oops: 17 [#1] SMP ARM
PC is at cfg80211_scan_done+0x28/0xc4 [cfg80211]
LR is at __ieee80211_scan_completed+0xe4/0x2dc [mac80211]
[<bf0077b0>] (cfg80211_scan_done+0x28/0xc4 [cfg80211])
[<bf0973d4>] (__ieee80211_scan_completed+0xe4/0x2dc [mac80211])
[<bf0982cc>] (ieee80211_scan_work+0x94/0x4f0 [mac80211])
[<c005fd10>] (process_one_work+0x1b0/0x4a8)
[<c0060404>] (worker_thread+0x138/0x37c)
[<c0066d70>] (kthread+0xa4/0xb0)
Signed-off-by: Eliad Peller <eliad@wizery.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
-rw-r--r-- | net/wireless/core.c | 20 | ||||
-rw-r--r-- | net/wireless/core.h | 2 | ||||
-rw-r--r-- | net/wireless/scan.c | 16 |
3 files changed, 8 insertions, 30 deletions
diff --git a/net/wireless/core.c b/net/wireless/core.c index c0443558b7bf..1a92c6a0731f 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c | |||
@@ -203,17 +203,8 @@ void cfg80211_stop_p2p_device(struct cfg80211_registered_device *rdev, | |||
203 | 203 | ||
204 | rdev->opencount--; | 204 | rdev->opencount--; |
205 | 205 | ||
206 | if (rdev->scan_req && rdev->scan_req->wdev == wdev) { | 206 | WARN_ON(rdev->scan_req && rdev->scan_req->wdev == wdev && |
207 | /* | 207 | !rdev->scan_req->notified); |
208 | * If the scan request wasn't notified as done, set it | ||
209 | * to aborted and leak it after a warning. The driver | ||
210 | * should have notified us that it ended at the latest | ||
211 | * during rdev_stop_p2p_device(). | ||
212 | */ | ||
213 | if (WARN_ON(!rdev->scan_req->notified)) | ||
214 | rdev->scan_req->aborted = true; | ||
215 | ___cfg80211_scan_done(rdev, !rdev->scan_req->notified); | ||
216 | } | ||
217 | } | 208 | } |
218 | 209 | ||
219 | static int cfg80211_rfkill_set_block(void *data, bool blocked) | 210 | static int cfg80211_rfkill_set_block(void *data, bool blocked) |
@@ -859,11 +850,8 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb, | |||
859 | break; | 850 | break; |
860 | case NETDEV_DOWN: | 851 | case NETDEV_DOWN: |
861 | cfg80211_update_iface_num(rdev, wdev->iftype, -1); | 852 | cfg80211_update_iface_num(rdev, wdev->iftype, -1); |
862 | if (rdev->scan_req && rdev->scan_req->wdev == wdev) { | 853 | WARN_ON(rdev->scan_req && rdev->scan_req->wdev == wdev && |
863 | if (WARN_ON(!rdev->scan_req->notified)) | 854 | !rdev->scan_req->notified); |
864 | rdev->scan_req->aborted = true; | ||
865 | ___cfg80211_scan_done(rdev, true); | ||
866 | } | ||
867 | 855 | ||
868 | if (WARN_ON(rdev->sched_scan_req && | 856 | if (WARN_ON(rdev->sched_scan_req && |
869 | rdev->sched_scan_req->dev == wdev->netdev)) { | 857 | rdev->sched_scan_req->dev == wdev->netdev)) { |
diff --git a/net/wireless/core.h b/net/wireless/core.h index ac77644cf894..453c6ed880f1 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h | |||
@@ -361,7 +361,7 @@ int cfg80211_validate_key_settings(struct cfg80211_registered_device *rdev, | |||
361 | struct key_params *params, int key_idx, | 361 | struct key_params *params, int key_idx, |
362 | bool pairwise, const u8 *mac_addr); | 362 | bool pairwise, const u8 *mac_addr); |
363 | void __cfg80211_scan_done(struct work_struct *wk); | 363 | void __cfg80211_scan_done(struct work_struct *wk); |
364 | void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev, bool leak); | 364 | void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev); |
365 | void __cfg80211_sched_scan_results(struct work_struct *wk); | 365 | void __cfg80211_sched_scan_results(struct work_struct *wk); |
366 | int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev, | 366 | int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev, |
367 | bool driver_initiated); | 367 | bool driver_initiated); |
diff --git a/net/wireless/scan.c b/net/wireless/scan.c index d4397eba5408..a32d52a04c27 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c | |||
@@ -161,7 +161,7 @@ static void __cfg80211_bss_expire(struct cfg80211_registered_device *dev, | |||
161 | dev->bss_generation++; | 161 | dev->bss_generation++; |
162 | } | 162 | } |
163 | 163 | ||
164 | void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev, bool leak) | 164 | void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev) |
165 | { | 165 | { |
166 | struct cfg80211_scan_request *request; | 166 | struct cfg80211_scan_request *request; |
167 | struct wireless_dev *wdev; | 167 | struct wireless_dev *wdev; |
@@ -210,17 +210,7 @@ void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev, bool leak) | |||
210 | dev_put(wdev->netdev); | 210 | dev_put(wdev->netdev); |
211 | 211 | ||
212 | rdev->scan_req = NULL; | 212 | rdev->scan_req = NULL; |
213 | 213 | kfree(request); | |
214 | /* | ||
215 | * OK. If this is invoked with "leak" then we can't | ||
216 | * free this ... but we've cleaned it up anyway. The | ||
217 | * driver failed to call the scan_done callback, so | ||
218 | * all bets are off, it might still be trying to use | ||
219 | * the scan request or not ... if it accesses the dev | ||
220 | * in there (it shouldn't anyway) then it may crash. | ||
221 | */ | ||
222 | if (!leak) | ||
223 | kfree(request); | ||
224 | } | 214 | } |
225 | 215 | ||
226 | void __cfg80211_scan_done(struct work_struct *wk) | 216 | void __cfg80211_scan_done(struct work_struct *wk) |
@@ -231,7 +221,7 @@ void __cfg80211_scan_done(struct work_struct *wk) | |||
231 | scan_done_wk); | 221 | scan_done_wk); |
232 | 222 | ||
233 | rtnl_lock(); | 223 | rtnl_lock(); |
234 | ___cfg80211_scan_done(rdev, false); | 224 | ___cfg80211_scan_done(rdev); |
235 | rtnl_unlock(); | 225 | rtnl_unlock(); |
236 | } | 226 | } |
237 | 227 | ||