mac80211: fix HT-Control field reception for management frames
authorJohannes Berg <johannes.berg@intel.com>
Tue, 26 May 2020 12:33:48 +0000 (14:33 +0200)
committerJohannes Berg <johannes.berg@intel.com>
Wed, 27 May 2020 08:03:27 +0000 (10:03 +0200)
If we receive management frames with an HT-Control field, we cannot
parse them properly, as we assume a fixed length management header.

Since we don't even need the HTC field (for these frames, or really
at all), just remove it at the beginning of RX.

Reported-by: Haggai Abramovsky <haggai.abramovsky@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Link: https://lore.kernel.org/r/20200526143346.cf5ce70521c5.I333251a084ec4cfe67b7ef7efe2d2f1a33883931@changeid
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
net/mac80211/rx.c

index 8b0fa5e345f48f94a5ea9b7babfab8b197288c90..21854a61a2b741e9c38fea837c4730102596f477 100644 (file)
@@ -93,13 +93,44 @@ static u8 *ieee80211_get_bssid(struct ieee80211_hdr *hdr, size_t len,
  * This function cleans up the SKB, i.e. it removes all the stuff
  * only useful for monitoring.
  */
-static void remove_monitor_info(struct sk_buff *skb,
-                               unsigned int present_fcs_len,
-                               unsigned int rtap_space)
+static struct sk_buff *ieee80211_clean_skb(struct sk_buff *skb,
+                                          unsigned int present_fcs_len,
+                                          unsigned int rtap_space)
 {
+       struct ieee80211_hdr *hdr;
+       unsigned int hdrlen;
+       __le16 fc;
+
        if (present_fcs_len)
                __pskb_trim(skb, skb->len - present_fcs_len);
        __pskb_pull(skb, rtap_space);
+
+       hdr = (void *)skb->data;
+       fc = hdr->frame_control;
+
+       /*
+        * Remove the HT-Control field (if present) on management
+        * frames after we've sent the frame to monitoring. We
+        * (currently) don't need it, and don't properly parse
+        * frames with it present, due to the assumption of a
+        * fixed management header length.
+        */
+       if (likely(!ieee80211_is_mgmt(fc) || !ieee80211_has_order(fc)))
+               return skb;
+
+       hdrlen = ieee80211_hdrlen(fc);
+       hdr->frame_control &= ~cpu_to_le16(IEEE80211_FCTL_ORDER);
+
+       if (!pskb_may_pull(skb, hdrlen)) {
+               dev_kfree_skb(skb);
+               return NULL;
+       }
+
+       memmove(skb->data + IEEE80211_HT_CTL_LEN, skb->data,
+               hdrlen - IEEE80211_HT_CTL_LEN);
+       __pskb_pull(skb, IEEE80211_HT_CTL_LEN);
+
+       return skb;
 }
 
 static inline bool should_drop_frame(struct sk_buff *skb, int present_fcs_len,
@@ -827,8 +858,8 @@ ieee80211_rx_monitor(struct ieee80211_local *local, struct sk_buff *origskb,
                        return NULL;
                }
 
-               remove_monitor_info(origskb, present_fcs_len, rtap_space);
-               return origskb;
+               return ieee80211_clean_skb(origskb, present_fcs_len,
+                                          rtap_space);
        }
 
        ieee80211_handle_mu_mimo_mon(monitor_sdata, origskb, rtap_space);
@@ -871,8 +902,7 @@ ieee80211_rx_monitor(struct ieee80211_local *local, struct sk_buff *origskb,
        if (!origskb)
                return NULL;
 
-       remove_monitor_info(origskb, present_fcs_len, rtap_space);
-       return origskb;
+       return ieee80211_clean_skb(origskb, present_fcs_len, rtap_space);
 }
 
 static void ieee80211_parse_qos(struct ieee80211_rx_data *rx)