| From foo@baz Thu Oct 5 10:28:31 CEST 2017 |
| From: Michal Kazior <michal.kazior@tieto.com> |
| Date: Thu, 12 Jan 2017 16:14:30 +0100 |
| Subject: ath10k: prevent sta pointer rcu violation |
| |
| From: Michal Kazior <michal.kazior@tieto.com> |
| |
| |
| [ Upstream commit 0a744d927406389e00687560d9ce3c5ab0e58db9 ] |
| |
| Station pointers are RCU protected so driver must |
| be extra careful if it tries to store them |
| internally for later use outside of the RCU |
| section it obtained it in. |
| |
| It was possible for station teardown to race with |
| some htt events. The possible outcome could be a |
| use-after-free and a crash. |
| |
| Only peer-flow-control capable firmware was |
| affected (so hardware-wise qca99x0 and qca4019). |
| |
| This could be done in sta_state() itself via |
| explicit synchronize_net() call but there's |
| already a convenient sta_pre_rcu_remove() op that |
| can be hooked up to avoid extra rcu stall. |
| |
| The peer->sta pointer itself can't be set to |
| NULL/ERR_PTR because it is later used in |
| sta_state() for extra sanity checks. |
| |
| Signed-off-by: Michal Kazior <michal.kazior@tieto.com> |
| Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com> |
| Signed-off-by: Sasha Levin <alexander.levin@verizon.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| drivers/net/wireless/ath/ath10k/core.h | 1 + |
| drivers/net/wireless/ath/ath10k/mac.c | 18 ++++++++++++++++++ |
| 2 files changed, 19 insertions(+) |
| |
| --- a/drivers/net/wireless/ath/ath10k/core.h |
| +++ b/drivers/net/wireless/ath/ath10k/core.h |
| @@ -314,6 +314,7 @@ struct ath10k_peer { |
| struct ieee80211_vif *vif; |
| struct ieee80211_sta *sta; |
| |
| + bool removed; |
| int vdev_id; |
| u8 addr[ETH_ALEN]; |
| DECLARE_BITMAP(peer_ids, ATH10K_MAX_NUM_PEER_IDS); |
| --- a/drivers/net/wireless/ath/ath10k/mac.c |
| +++ b/drivers/net/wireless/ath/ath10k/mac.c |
| @@ -3738,6 +3738,9 @@ struct ieee80211_txq *ath10k_mac_txq_loo |
| if (!peer) |
| return NULL; |
| |
| + if (peer->removed) |
| + return NULL; |
| + |
| if (peer->sta) |
| return peer->sta->txq[tid]; |
| else if (peer->vif) |
| @@ -7422,6 +7425,20 @@ ath10k_mac_op_switch_vif_chanctx(struct |
| return 0; |
| } |
| |
| +static void ath10k_mac_op_sta_pre_rcu_remove(struct ieee80211_hw *hw, |
| + struct ieee80211_vif *vif, |
| + struct ieee80211_sta *sta) |
| +{ |
| + struct ath10k *ar; |
| + struct ath10k_peer *peer; |
| + |
| + ar = hw->priv; |
| + |
| + list_for_each_entry(peer, &ar->peers, list) |
| + if (peer->sta == sta) |
| + peer->removed = true; |
| +} |
| + |
| static const struct ieee80211_ops ath10k_ops = { |
| .tx = ath10k_mac_op_tx, |
| .wake_tx_queue = ath10k_mac_op_wake_tx_queue, |
| @@ -7462,6 +7479,7 @@ static const struct ieee80211_ops ath10k |
| .assign_vif_chanctx = ath10k_mac_op_assign_vif_chanctx, |
| .unassign_vif_chanctx = ath10k_mac_op_unassign_vif_chanctx, |
| .switch_vif_chanctx = ath10k_mac_op_switch_vif_chanctx, |
| + .sta_pre_rcu_remove = ath10k_mac_op_sta_pre_rcu_remove, |
| |
| CFG80211_TESTMODE_CMD(ath10k_tm_cmd) |
| |