ath10k: prevent sta pointer rcu violation
authorMichal Kazior <michal.kazior@tieto.com>
Thu, 12 Jan 2017 15:14:30 +0000 (16:14 +0100)
committerKalle Valo <kvalo@qca.qualcomm.com>
Thu, 19 Jan 2017 13:17:58 +0000 (15:17 +0200)
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>
drivers/net/wireless/ath/ath10k/core.h
drivers/net/wireless/ath/ath10k/mac.c

index c7664d6569facdee52cb499566a9287ff8731759..1ab589296dff1175b7ee40cb1d8e7f6b72e7e405 100644 (file)
@@ -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);
index 28bf19914fff1cfbcc1f6dbd26520ff759e2fefe..9977829a6ec446d733416e8269fa32dd202aa2f5 100644 (file)
@@ -3805,6 +3805,9 @@ struct ieee80211_txq *ath10k_mac_txq_lookup(struct ath10k *ar,
        if (!peer)
                return NULL;
 
+       if (peer->removed)
+               return NULL;
+
        if (peer->sta)
                return peer->sta->txq[tid];
        else if (peer->vif)
@@ -7517,6 +7520,20 @@ ath10k_mac_op_switch_vif_chanctx(struct ieee80211_hw *hw,
        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,
@@ -7557,6 +7574,7 @@ static const struct ieee80211_ops ath10k_ops = {
        .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)