mac80211: backport upstream fixes for FragAttacks
[openwrt/openwrt.git] / package / kernel / mac80211 / patches / ath / 300-ath10k-add-CCMP-PN-replay-protection-for-fragmented-.patch
diff --git a/package/kernel/mac80211/patches/ath/300-ath10k-add-CCMP-PN-replay-protection-for-fragmented-.patch b/package/kernel/mac80211/patches/ath/300-ath10k-add-CCMP-PN-replay-protection-for-fragmented-.patch
new file mode 100644 (file)
index 0000000..0ce49b2
--- /dev/null
@@ -0,0 +1,180 @@
+From: Wen Gong <wgong@codeaurora.org>
+Date: Tue, 11 May 2021 20:02:52 +0200
+Subject: [PATCH] ath10k: add CCMP PN replay protection for fragmented
+ frames for PCIe
+
+PN replay check for not fragmented frames is finished in the firmware,
+but this was not done for fragmented frames when ath10k is used with
+QCA6174/QCA6377 PCIe. mac80211 has the function
+ieee80211_rx_h_defragment() for PN replay check for fragmented frames,
+but this does not get checked with QCA6174 due to the
+ieee80211_has_protected() condition not matching the cleared Protected
+bit case.
+
+Validate the PN of received fragmented frames within ath10k when CCMP is
+used and drop the fragment if the PN is not correct (incremented by
+exactly one from the previous fragment). This applies only for
+QCA6174/QCA6377 PCIe.
+
+Tested-on: QCA6174 hw3.2 PCI WLAN.RM.4.4.1-00110-QCARMSWP-1
+
+Cc: stable@vger.kernel.org
+Signed-off-by: Wen Gong <wgong@codeaurora.org>
+Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
+Signed-off-by: Johannes Berg <johannes.berg@intel.com>
+---
+
+--- a/drivers/net/wireless/ath/ath10k/htt.h
++++ b/drivers/net/wireless/ath/ath10k/htt.h
+@@ -846,6 +846,7 @@ enum htt_security_types {
+ #define ATH10K_HTT_TXRX_PEER_SECURITY_MAX 2
+ #define ATH10K_TXRX_NUM_EXT_TIDS 19
++#define ATH10K_TXRX_NON_QOS_TID 16
+ enum htt_security_flags {
+ #define HTT_SECURITY_TYPE_MASK 0x7F
+--- a/drivers/net/wireless/ath/ath10k/htt_rx.c
++++ b/drivers/net/wireless/ath/ath10k/htt_rx.c
+@@ -1746,16 +1746,87 @@ static void ath10k_htt_rx_h_csum_offload
+       msdu->ip_summed = ath10k_htt_rx_get_csum_state(msdu);
+ }
++static u64 ath10k_htt_rx_h_get_pn(struct ath10k *ar, struct sk_buff *skb,
++                                u16 offset,
++                                enum htt_rx_mpdu_encrypt_type enctype)
++{
++      struct ieee80211_hdr *hdr;
++      u64 pn = 0;
++      u8 *ehdr;
++
++      hdr = (struct ieee80211_hdr *)(skb->data + offset);
++      ehdr = skb->data + offset + ieee80211_hdrlen(hdr->frame_control);
++
++      if (enctype == HTT_RX_MPDU_ENCRYPT_AES_CCM_WPA2) {
++              pn = ehdr[0];
++              pn |= (u64)ehdr[1] << 8;
++              pn |= (u64)ehdr[4] << 16;
++              pn |= (u64)ehdr[5] << 24;
++              pn |= (u64)ehdr[6] << 32;
++              pn |= (u64)ehdr[7] << 40;
++      }
++      return pn;
++}
++
++static bool ath10k_htt_rx_h_frag_pn_check(struct ath10k *ar,
++                                        struct sk_buff *skb,
++                                        u16 peer_id,
++                                        u16 offset,
++                                        enum htt_rx_mpdu_encrypt_type enctype)
++{
++      struct ath10k_peer *peer;
++      union htt_rx_pn_t *last_pn, new_pn = {0};
++      struct ieee80211_hdr *hdr;
++      bool more_frags;
++      u8 tid, frag_number;
++      u32 seq;
++
++      peer = ath10k_peer_find_by_id(ar, peer_id);
++      if (!peer) {
++              ath10k_dbg(ar, ATH10K_DBG_HTT, "invalid peer for frag pn check\n");
++              return false;
++      }
++
++      hdr = (struct ieee80211_hdr *)(skb->data + offset);
++      if (ieee80211_is_data_qos(hdr->frame_control))
++              tid = ieee80211_get_tid(hdr);
++      else
++              tid = ATH10K_TXRX_NON_QOS_TID;
++
++      last_pn = &peer->frag_tids_last_pn[tid];
++      new_pn.pn48 = ath10k_htt_rx_h_get_pn(ar, skb, offset, enctype);
++      more_frags = ieee80211_has_morefrags(hdr->frame_control);
++      frag_number = le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_FRAG;
++      seq = (__le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_SEQ) >> 4;
++
++      if (frag_number == 0) {
++              last_pn->pn48 = new_pn.pn48;
++              peer->frag_tids_seq[tid] = seq;
++      } else {
++              if (seq != peer->frag_tids_seq[tid])
++                      return false;
++
++              if (new_pn.pn48 != last_pn->pn48 + 1)
++                      return false;
++
++              last_pn->pn48 = new_pn.pn48;
++      }
++
++      return true;
++}
++
+ static void ath10k_htt_rx_h_mpdu(struct ath10k *ar,
+                                struct sk_buff_head *amsdu,
+                                struct ieee80211_rx_status *status,
+                                bool fill_crypt_header,
+                                u8 *rx_hdr,
+-                               enum ath10k_pkt_rx_err *err)
++                               enum ath10k_pkt_rx_err *err,
++                               u16 peer_id,
++                               bool frag)
+ {
+       struct sk_buff *first;
+       struct sk_buff *last;
+-      struct sk_buff *msdu;
++      struct sk_buff *msdu, *temp;
+       struct htt_rx_desc *rxd;
+       struct ieee80211_hdr *hdr;
+       enum htt_rx_mpdu_encrypt_type enctype;
+@@ -1768,6 +1839,7 @@ static void ath10k_htt_rx_h_mpdu(struct
+       bool is_decrypted;
+       bool is_mgmt;
+       u32 attention;
++      bool frag_pn_check = true;
+       if (skb_queue_empty(amsdu))
+               return;
+@@ -1866,6 +1938,24 @@ static void ath10k_htt_rx_h_mpdu(struct
+       }
+       skb_queue_walk(amsdu, msdu) {
++              if (frag && !fill_crypt_header && is_decrypted &&
++                  enctype == HTT_RX_MPDU_ENCRYPT_AES_CCM_WPA2)
++                      frag_pn_check = ath10k_htt_rx_h_frag_pn_check(ar,
++                                                                    msdu,
++                                                                    peer_id,
++                                                                    0,
++                                                                    enctype);
++
++              if (!frag_pn_check) {
++                      /* Discard the fragment with invalid PN */
++                      temp = msdu->prev;
++                      __skb_unlink(msdu, amsdu);
++                      dev_kfree_skb_any(msdu);
++                      msdu = temp;
++                      frag_pn_check = true;
++                      continue;
++              }
++
+               ath10k_htt_rx_h_csum_offload(msdu);
+               ath10k_htt_rx_h_undecap(ar, msdu, status, first_hdr, enctype,
+                                       is_decrypted);
+@@ -2071,7 +2161,8 @@ static int ath10k_htt_rx_handle_amsdu(st
+               ath10k_htt_rx_h_unchain(ar, &amsdu, &drop_cnt, &unchain_cnt);
+       ath10k_htt_rx_h_filter(ar, &amsdu, rx_status, &drop_cnt_filter);
+-      ath10k_htt_rx_h_mpdu(ar, &amsdu, rx_status, true, first_hdr, &err);
++      ath10k_htt_rx_h_mpdu(ar, &amsdu, rx_status, true, first_hdr, &err, 0,
++                           false);
+       msdus_to_queue = skb_queue_len(&amsdu);
+       ath10k_htt_rx_h_enqueue(ar, &amsdu, rx_status);
+@@ -3027,7 +3118,7 @@ static int ath10k_htt_rx_in_ord_ind(stru
+                       ath10k_htt_rx_h_ppdu(ar, &amsdu, status, vdev_id);
+                       ath10k_htt_rx_h_filter(ar, &amsdu, status, NULL);
+                       ath10k_htt_rx_h_mpdu(ar, &amsdu, status, false, NULL,
+-                                           NULL);
++                                           NULL, peer_id, frag);
+                       ath10k_htt_rx_h_enqueue(ar, &amsdu, status);
+                       break;
+               case -EAGAIN: