| From 3b091ad48be89ecb73c878647a99c603c8b4cb11 Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Tue, 29 Aug 2023 20:16:11 +0200 |
| Subject: wifi: mac80211: fix BA session teardown race |
| |
| From: Johannes Berg <johannes.berg@intel.com> |
| |
| [ Upstream commit 05f136220d17839eb7c155f015ace9152f603225 ] |
| |
| As previously reported by Alexander, whose commit 69403bad97aa |
| ("wifi: mac80211: sdata can be NULL during AMPDU start") I'm |
| reverting as part of this commit, there's a race between station |
| destruction and aggregation setup, where the aggregation setup |
| can happen while the station is being removed and queue the work |
| after ieee80211_sta_tear_down_BA_sessions() has already run in |
| __sta_info_destroy_part1(), and thus the worker will run with a |
| now freed station. In his case, this manifested in a NULL sdata |
| pointer, but really there's no guarantee whatsoever. |
| |
| The real issue seems to be that it's possible at all to have a |
| situation where this occurs - we want to stop the BA sessions |
| when doing _part1, but we cannot be sure, and WLAN_STA_BLOCK_BA |
| isn't necessarily effective since we don't know that the setup |
| isn't concurrently running and already got past the check. |
| |
| Simply call ieee80211_sta_tear_down_BA_sessions() again in the |
| second part of station destruction, since at that point really |
| nothing else can hold a reference to the station any more. |
| |
| Also revert the sdata checks since those are just misleading at |
| this point. |
| |
| Signed-off-by: Johannes Berg <johannes.berg@intel.com> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| net/mac80211/agg-tx.c | 6 +----- |
| net/mac80211/driver-ops.c | 3 --- |
| net/mac80211/sta_info.c | 14 ++++++++++++++ |
| 3 files changed, 15 insertions(+), 8 deletions(-) |
| |
| diff --git a/net/mac80211/agg-tx.c b/net/mac80211/agg-tx.c |
| index b6b7726858815..0a69e47f1c55f 100644 |
| --- a/net/mac80211/agg-tx.c |
| +++ b/net/mac80211/agg-tx.c |
| @@ -497,7 +497,7 @@ void ieee80211_tx_ba_session_handle_start(struct sta_info *sta, int tid) |
| { |
| struct tid_ampdu_tx *tid_tx; |
| struct ieee80211_local *local = sta->local; |
| - struct ieee80211_sub_if_data *sdata; |
| + struct ieee80211_sub_if_data *sdata = sta->sdata; |
| struct ieee80211_ampdu_params params = { |
| .sta = &sta->sta, |
| .action = IEEE80211_AMPDU_TX_START, |
| @@ -525,7 +525,6 @@ void ieee80211_tx_ba_session_handle_start(struct sta_info *sta, int tid) |
| */ |
| synchronize_net(); |
| |
| - sdata = sta->sdata; |
| params.ssn = sta->tid_seq[tid] >> 4; |
| ret = drv_ampdu_action(local, sdata, ¶ms); |
| tid_tx->ssn = params.ssn; |
| @@ -539,9 +538,6 @@ void ieee80211_tx_ba_session_handle_start(struct sta_info *sta, int tid) |
| */ |
| set_bit(HT_AGG_STATE_DRV_READY, &tid_tx->state); |
| } else if (ret) { |
| - if (!sdata) |
| - return; |
| - |
| ht_dbg(sdata, |
| "BA request denied - HW unavailable for %pM tid %d\n", |
| sta->sta.addr, tid); |
| diff --git a/net/mac80211/driver-ops.c b/net/mac80211/driver-ops.c |
| index f8af0c3d405ae..d6478fd00badf 100644 |
| --- a/net/mac80211/driver-ops.c |
| +++ b/net/mac80211/driver-ops.c |
| @@ -393,9 +393,6 @@ int drv_ampdu_action(struct ieee80211_local *local, |
| |
| might_sleep(); |
| |
| - if (!sdata) |
| - return -EIO; |
| - |
| sdata = get_bss_sdata(sdata); |
| if (!check_sdata_in_driver(sdata)) |
| return -EIO; |
| diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c |
| index 984f8f67492fd..42ba51a9700f2 100644 |
| --- a/net/mac80211/sta_info.c |
| +++ b/net/mac80211/sta_info.c |
| @@ -1402,6 +1402,20 @@ static void __sta_info_destroy_part2(struct sta_info *sta, bool recalc) |
| * after _part1 and before _part2! |
| */ |
| |
| + /* |
| + * There's a potential race in _part1 where we set WLAN_STA_BLOCK_BA |
| + * but someone might have just gotten past a check, and not yet into |
| + * queuing the work/creating the data/etc. |
| + * |
| + * Do another round of destruction so that the worker is certainly |
| + * canceled before we later free the station. |
| + * |
| + * Since this is after synchronize_rcu()/synchronize_net() we're now |
| + * certain that nobody can actually hold a reference to the STA and |
| + * be calling e.g. ieee80211_start_tx_ba_session(). |
| + */ |
| + ieee80211_sta_tear_down_BA_sessions(sta, AGG_STOP_DESTROY_STA); |
| + |
| might_sleep(); |
| lockdep_assert_held(&local->sta_mtx); |
| |
| -- |
| 2.43.0 |
| |