aboutsummaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorJohannes Berg2013-03-25 05:51:14 -0500
committerGreg Kroah-Hartman2013-04-12 11:52:07 -0500
commit5c4a5a843043493ee5ddd9978517c4357a7245ee (patch)
tree6202f7aabd67e24da67990e61ec55d913d827faf /net
parent86489ad1d0346c94a575c2e370a57d3ffb9bd2ad (diff)
downloadkernel-video-5c4a5a843043493ee5ddd9978517c4357a7245ee.tar.gz
kernel-video-5c4a5a843043493ee5ddd9978517c4357a7245ee.tar.xz
kernel-video-5c4a5a843043493ee5ddd9978517c4357a7245ee.zip
mac80211: fix remain-on-channel cancel crash
commit 3fbd45ca8d1c98f3c2582ef8bc70ade42f70947b upstream. If a ROC item is canceled just as it expires, the work struct may be scheduled while it is running (and waiting for the mutex). This results in it being run after being freed, which obviously crashes. To fix this don't free it when aborting is requested but instead mark it as "to be freed", which makes the work a no-op and allows freeing it outside. Reported-by: Jouni Malinen <j@w1.fi> Tested-by: Jouni Malinen <j@w1.fi> Signed-off-by: Johannes Berg <johannes.berg@intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'net')
-rw-r--r--net/mac80211/cfg.c6
-rw-r--r--net/mac80211/ieee80211_i.h3
-rw-r--r--net/mac80211/offchannel.c23
3 files changed, 23 insertions, 9 deletions
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 0479c64aa83..49c48c6bae1 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -2499,7 +2499,7 @@ static int ieee80211_cancel_roc(struct ieee80211_local *local,
2499 list_del(&dep->list); 2499 list_del(&dep->list);
2500 mutex_unlock(&local->mtx); 2500 mutex_unlock(&local->mtx);
2501 2501
2502 ieee80211_roc_notify_destroy(dep); 2502 ieee80211_roc_notify_destroy(dep, true);
2503 return 0; 2503 return 0;
2504 } 2504 }
2505 2505
@@ -2539,7 +2539,7 @@ static int ieee80211_cancel_roc(struct ieee80211_local *local,
2539 ieee80211_start_next_roc(local); 2539 ieee80211_start_next_roc(local);
2540 mutex_unlock(&local->mtx); 2540 mutex_unlock(&local->mtx);
2541 2541
2542 ieee80211_roc_notify_destroy(found); 2542 ieee80211_roc_notify_destroy(found, true);
2543 } else { 2543 } else {
2544 /* work may be pending so use it all the time */ 2544 /* work may be pending so use it all the time */
2545 found->abort = true; 2545 found->abort = true;
@@ -2549,6 +2549,8 @@ static int ieee80211_cancel_roc(struct ieee80211_local *local,
2549 2549
2550 /* work will clean up etc */ 2550 /* work will clean up etc */
2551 flush_delayed_work(&found->work); 2551 flush_delayed_work(&found->work);
2552 WARN_ON(!found->to_be_freed);
2553 kfree(found);
2552 } 2554 }
2553 2555
2554 return 0; 2556 return 0;
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 2ed065c0956..55d8f89bd58 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -346,6 +346,7 @@ struct ieee80211_roc_work {
346 struct ieee80211_channel *chan; 346 struct ieee80211_channel *chan;
347 347
348 bool started, abort, hw_begun, notified; 348 bool started, abort, hw_begun, notified;
349 bool to_be_freed;
349 350
350 unsigned long hw_start_time; 351 unsigned long hw_start_time;
351 352
@@ -1363,7 +1364,7 @@ void ieee80211_offchannel_return(struct ieee80211_local *local);
1363void ieee80211_roc_setup(struct ieee80211_local *local); 1364void ieee80211_roc_setup(struct ieee80211_local *local);
1364void ieee80211_start_next_roc(struct ieee80211_local *local); 1365void ieee80211_start_next_roc(struct ieee80211_local *local);
1365void ieee80211_roc_purge(struct ieee80211_sub_if_data *sdata); 1366void ieee80211_roc_purge(struct ieee80211_sub_if_data *sdata);
1366void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc); 1367void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc, bool free);
1367void ieee80211_sw_roc_work(struct work_struct *work); 1368void ieee80211_sw_roc_work(struct work_struct *work);
1368void ieee80211_handle_roc_started(struct ieee80211_roc_work *roc); 1369void ieee80211_handle_roc_started(struct ieee80211_roc_work *roc);
1369 1370
diff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c
index a3ad4c3c80a..7acbdaa77a5 100644
--- a/net/mac80211/offchannel.c
+++ b/net/mac80211/offchannel.c
@@ -299,10 +299,13 @@ void ieee80211_start_next_roc(struct ieee80211_local *local)
299 } 299 }
300} 300}
301 301
302void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc) 302void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc, bool free)
303{ 303{
304 struct ieee80211_roc_work *dep, *tmp; 304 struct ieee80211_roc_work *dep, *tmp;
305 305
306 if (WARN_ON(roc->to_be_freed))
307 return;
308
306 /* was never transmitted */ 309 /* was never transmitted */
307 if (roc->frame) { 310 if (roc->frame) {
308 cfg80211_mgmt_tx_status(&roc->sdata->wdev, 311 cfg80211_mgmt_tx_status(&roc->sdata->wdev,
@@ -318,9 +321,12 @@ void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc)
318 GFP_KERNEL); 321 GFP_KERNEL);
319 322
320 list_for_each_entry_safe(dep, tmp, &roc->dependents, list) 323 list_for_each_entry_safe(dep, tmp, &roc->dependents, list)
321 ieee80211_roc_notify_destroy(dep); 324 ieee80211_roc_notify_destroy(dep, true);
322 325
323 kfree(roc); 326 if (free)
327 kfree(roc);
328 else
329 roc->to_be_freed = true;
324} 330}
325 331
326void ieee80211_sw_roc_work(struct work_struct *work) 332void ieee80211_sw_roc_work(struct work_struct *work)
@@ -333,6 +339,9 @@ void ieee80211_sw_roc_work(struct work_struct *work)
333 339
334 mutex_lock(&local->mtx); 340 mutex_lock(&local->mtx);
335 341
342 if (roc->to_be_freed)
343 goto out_unlock;
344
336 if (roc->abort) 345 if (roc->abort)
337 goto finish; 346 goto finish;
338 347
@@ -372,7 +381,7 @@ void ieee80211_sw_roc_work(struct work_struct *work)
372 finish: 381 finish:
373 list_del(&roc->list); 382 list_del(&roc->list);
374 started = roc->started; 383 started = roc->started;
375 ieee80211_roc_notify_destroy(roc); 384 ieee80211_roc_notify_destroy(roc, !roc->abort);
376 385
377 if (started) { 386 if (started) {
378 drv_flush(local, false); 387 drv_flush(local, false);
@@ -412,7 +421,7 @@ static void ieee80211_hw_roc_done(struct work_struct *work)
412 421
413 list_del(&roc->list); 422 list_del(&roc->list);
414 423
415 ieee80211_roc_notify_destroy(roc); 424 ieee80211_roc_notify_destroy(roc, true);
416 425
417 /* if there's another roc, start it now */ 426 /* if there's another roc, start it now */
418 ieee80211_start_next_roc(local); 427 ieee80211_start_next_roc(local);
@@ -462,12 +471,14 @@ void ieee80211_roc_purge(struct ieee80211_sub_if_data *sdata)
462 list_for_each_entry_safe(roc, tmp, &tmp_list, list) { 471 list_for_each_entry_safe(roc, tmp, &tmp_list, list) {
463 if (local->ops->remain_on_channel) { 472 if (local->ops->remain_on_channel) {
464 list_del(&roc->list); 473 list_del(&roc->list);
465 ieee80211_roc_notify_destroy(roc); 474 ieee80211_roc_notify_destroy(roc, true);
466 } else { 475 } else {
467 ieee80211_queue_delayed_work(&local->hw, &roc->work, 0); 476 ieee80211_queue_delayed_work(&local->hw, &roc->work, 0);
468 477
469 /* work will clean up etc */ 478 /* work will clean up etc */
470 flush_delayed_work(&roc->work); 479 flush_delayed_work(&roc->work);
480 WARN_ON(!roc->to_be_freed);
481 kfree(roc);
471 } 482 }
472 } 483 }
473 484