Skip to content

Commit

Permalink
mac80211: fix remain-on-channel cancel crash
Browse files Browse the repository at this point in the history
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.

Cc: [email protected] [3.6+]
Reported-by: Jouni Malinen <[email protected]>
Tested-by: Jouni Malinen <[email protected]>
Signed-off-by: Johannes Berg <[email protected]>
  • Loading branch information
jmberg-intel committed Mar 25, 2013
1 parent 370bd00 commit 3fbd45c
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 9 deletions.
6 changes: 4 additions & 2 deletions net/mac80211/cfg.c
Original file line number Diff line number Diff line change
Expand Up @@ -2582,7 +2582,7 @@ static int ieee80211_cancel_roc(struct ieee80211_local *local,
list_del(&dep->list);
mutex_unlock(&local->mtx);

ieee80211_roc_notify_destroy(dep);
ieee80211_roc_notify_destroy(dep, true);
return 0;
}

Expand Down Expand Up @@ -2622,7 +2622,7 @@ static int ieee80211_cancel_roc(struct ieee80211_local *local,
ieee80211_start_next_roc(local);
mutex_unlock(&local->mtx);

ieee80211_roc_notify_destroy(found);
ieee80211_roc_notify_destroy(found, true);
} else {
/* work may be pending so use it all the time */
found->abort = true;
Expand All @@ -2632,6 +2632,8 @@ static int ieee80211_cancel_roc(struct ieee80211_local *local,

/* work will clean up etc */
flush_delayed_work(&found->work);
WARN_ON(!found->to_be_freed);
kfree(found);
}

return 0;
Expand Down
3 changes: 2 additions & 1 deletion net/mac80211/ieee80211_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ struct ieee80211_roc_work {
struct ieee80211_channel *chan;

bool started, abort, hw_begun, notified;
bool to_be_freed;

unsigned long hw_start_time;

Expand Down Expand Up @@ -1347,7 +1348,7 @@ void ieee80211_offchannel_return(struct ieee80211_local *local);
void ieee80211_roc_setup(struct ieee80211_local *local);
void ieee80211_start_next_roc(struct ieee80211_local *local);
void ieee80211_roc_purge(struct ieee80211_sub_if_data *sdata);
void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc);
void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc, bool free);
void ieee80211_sw_roc_work(struct work_struct *work);
void ieee80211_handle_roc_started(struct ieee80211_roc_work *roc);

Expand Down
23 changes: 17 additions & 6 deletions net/mac80211/offchannel.c
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,13 @@ void ieee80211_start_next_roc(struct ieee80211_local *local)
}
}

void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc)
void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc, bool free)
{
struct ieee80211_roc_work *dep, *tmp;

if (WARN_ON(roc->to_be_freed))
return;

/* was never transmitted */
if (roc->frame) {
cfg80211_mgmt_tx_status(&roc->sdata->wdev,
Expand All @@ -316,9 +319,12 @@ void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc)
GFP_KERNEL);

list_for_each_entry_safe(dep, tmp, &roc->dependents, list)
ieee80211_roc_notify_destroy(dep);
ieee80211_roc_notify_destroy(dep, true);

kfree(roc);
if (free)
kfree(roc);
else
roc->to_be_freed = true;
}

void ieee80211_sw_roc_work(struct work_struct *work)
Expand All @@ -331,6 +337,9 @@ void ieee80211_sw_roc_work(struct work_struct *work)

mutex_lock(&local->mtx);

if (roc->to_be_freed)
goto out_unlock;

if (roc->abort)
goto finish;

Expand Down Expand Up @@ -370,7 +379,7 @@ void ieee80211_sw_roc_work(struct work_struct *work)
finish:
list_del(&roc->list);
started = roc->started;
ieee80211_roc_notify_destroy(roc);
ieee80211_roc_notify_destroy(roc, !roc->abort);

if (started) {
drv_flush(local, false);
Expand Down Expand Up @@ -410,7 +419,7 @@ static void ieee80211_hw_roc_done(struct work_struct *work)

list_del(&roc->list);

ieee80211_roc_notify_destroy(roc);
ieee80211_roc_notify_destroy(roc, true);

/* if there's another roc, start it now */
ieee80211_start_next_roc(local);
Expand Down Expand Up @@ -460,12 +469,14 @@ void ieee80211_roc_purge(struct ieee80211_sub_if_data *sdata)
list_for_each_entry_safe(roc, tmp, &tmp_list, list) {
if (local->ops->remain_on_channel) {
list_del(&roc->list);
ieee80211_roc_notify_destroy(roc);
ieee80211_roc_notify_destroy(roc, true);
} else {
ieee80211_queue_delayed_work(&local->hw, &roc->work, 0);

/* work will clean up etc */
flush_delayed_work(&roc->work);
WARN_ON(!roc->to_be_freed);
kfree(roc);
}
}

Expand Down

0 comments on commit 3fbd45c

Please sign in to comment.