mac80211: parse radiotap header when selecting Tx queue
authorMathy Vanhoef <Mathy.Vanhoef@kuleuven.be>
Thu, 23 Jul 2020 10:01:53 +0000 (14:01 +0400)
committerJohannes Berg <johannes.berg@intel.com>
Fri, 31 Jul 2020 07:27:01 +0000 (09:27 +0200)
Already parse the radiotap header in ieee80211_monitor_select_queue.
In a subsequent commit this will allow us to add a radiotap flag that
influences the queue on which injected packets will be sent.

This also fixes the incomplete validation of the injected frame in
ieee80211_monitor_select_queue: currently an out of bounds memory
access may occur in in the called function ieee80211_select_queue_80211
if the 802.11 header is too small.

Note that in ieee80211_monitor_start_xmit the radiotap header is parsed
again, which is necessairy because ieee80211_monitor_select_queue is not
always called beforehand.

Signed-off-by: Mathy Vanhoef <Mathy.Vanhoef@kuleuven.be>
Link: https://lore.kernel.org/r/20200723100153.31631-6-Mathy.Vanhoef@kuleuven.be
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
include/net/mac80211.h
net/mac80211/iface.c
net/mac80211/tx.c

index 21ce821..6e26f0b 100644 (file)
@@ -6239,6 +6239,14 @@ bool ieee80211_tx_prepare_skb(struct ieee80211_hw *hw,
                              int band, struct ieee80211_sta **sta);
 
 /**
+ * Sanity-check and parse the radiotap header of injected frames
+ * @skb: packet injected by userspace
+ * @dev: the &struct device of this 802.11 device
+ */
+bool ieee80211_parse_tx_radiotap(struct sk_buff *skb,
+                                struct net_device *dev);
+
+/**
  * struct ieee80211_noa_data - holds temporary data for tracking P2P NoA state
  *
  * @next_tsf: TSF timestamp of the next absent state change
index 570f818..9740ae8 100644 (file)
@@ -1183,17 +1183,24 @@ static u16 ieee80211_monitor_select_queue(struct net_device *dev,
 {
        struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
        struct ieee80211_local *local = sdata->local;
+       struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
        struct ieee80211_hdr *hdr;
-       struct ieee80211_radiotap_header *rtap = (void *)skb->data;
+       int len_rthdr;
 
        if (local->hw.queues < IEEE80211_NUM_ACS)
                return 0;
 
-       if (skb->len < 4 ||
-           skb->len < le16_to_cpu(rtap->it_len) + 2 /* frame control */)
+       /* reset flags and info before parsing radiotap header */
+       memset(info, 0, sizeof(*info));
+
+       if (!ieee80211_parse_tx_radiotap(skb, dev))
                return 0; /* doesn't matter, frame will be dropped */
 
-       hdr = (void *)((u8 *)skb->data + le16_to_cpu(rtap->it_len));
+       len_rthdr = ieee80211_get_radiotap_len(skb->data);
+       hdr = (struct ieee80211_hdr *)(skb->data + len_rthdr);
+       if (skb->len < len_rthdr + 2 ||
+           skb->len < len_rthdr + ieee80211_hdrlen(hdr->frame_control))
+               return 0; /* doesn't matter, frame will be dropped */
 
        return ieee80211_select_queue_80211(sdata, skb, hdr);
 }
index 7fb4afa..ec35a92 100644 (file)
@@ -2015,9 +2015,10 @@ void ieee80211_xmit(struct ieee80211_sub_if_data *sdata,
        ieee80211_tx(sdata, sta, skb, false);
 }
 
-static bool ieee80211_parse_tx_radiotap(struct ieee80211_local *local,
-                                       struct sk_buff *skb)
+bool ieee80211_parse_tx_radiotap(struct sk_buff *skb,
+                                struct net_device *dev)
 {
+       struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
        struct ieee80211_radiotap_iterator iterator;
        struct ieee80211_radiotap_header *rthdr =
                (struct ieee80211_radiotap_header *) skb->data;
@@ -2036,6 +2037,18 @@ static bool ieee80211_parse_tx_radiotap(struct ieee80211_local *local,
        u8 vht_mcs = 0, vht_nss = 0;
        int i;
 
+       /* check for not even having the fixed radiotap header part */
+       if (unlikely(skb->len < sizeof(struct ieee80211_radiotap_header)))
+               return false; /* too short to be possibly valid */
+
+       /* is it a header version we can trust to find length from? */
+       if (unlikely(rthdr->it_version))
+               return false; /* only version 0 is supported */
+
+       /* does the skb contain enough to deliver on the alleged length? */
+       if (unlikely(skb->len < ieee80211_get_radiotap_len(skb->data)))
+               return false; /* skb too short for claimed rt header extent */
+
        info->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT |
                       IEEE80211_TX_CTL_DONTFRAG;
 
@@ -2189,13 +2202,6 @@ static bool ieee80211_parse_tx_radiotap(struct ieee80211_local *local,
                                                     local->hw.max_rate_tries);
        }
 
-       /*
-        * remove the radiotap header
-        * iterator->_max_length was sanity-checked against
-        * skb->len by iterator init
-        */
-       skb_pull(skb, iterator._max_length);
-
        return true;
 }
 
@@ -2204,8 +2210,6 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb,
 {
        struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
        struct ieee80211_chanctx_conf *chanctx_conf;
-       struct ieee80211_radiotap_header *prthdr =
-               (struct ieee80211_radiotap_header *)skb->data;
        struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
        struct ieee80211_hdr *hdr;
        struct ieee80211_sub_if_data *tmp_sdata, *sdata;
@@ -2213,21 +2217,17 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb,
        u16 len_rthdr;
        int hdrlen;
 
-       /* check for not even having the fixed radiotap header part */
-       if (unlikely(skb->len < sizeof(struct ieee80211_radiotap_header)))
-               goto fail; /* too short to be possibly valid */
+       memset(info, 0, sizeof(*info));
+       info->flags = IEEE80211_TX_CTL_REQ_TX_STATUS |
+                     IEEE80211_TX_CTL_INJECTED;
 
-       /* is it a header version we can trust to find length from? */
-       if (unlikely(prthdr->it_version))
-               goto fail; /* only version 0 is supported */
+       /* Sanity-check and process the injection radiotap header */
+       if (!ieee80211_parse_tx_radiotap(skb, dev))
+               goto fail;
 
-       /* then there must be a radiotap header with a length we can use */
+       /* we now know there is a radiotap header with a length we can use */
        len_rthdr = ieee80211_get_radiotap_len(skb->data);
 
-       /* does the skb contain enough to deliver on the alleged length? */
-       if (unlikely(skb->len < len_rthdr))
-               goto fail; /* skb too short for claimed rt header extent */
-
        /*
         * fix up the pointers accounting for the radiotap
         * header still being in there.  We are being given
@@ -2273,11 +2273,6 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb,
                skb->priority = *p & IEEE80211_QOS_CTL_TAG1D_MASK;
        }
 
-       memset(info, 0, sizeof(*info));
-
-       info->flags = IEEE80211_TX_CTL_REQ_TX_STATUS |
-                     IEEE80211_TX_CTL_INJECTED;
-
        rcu_read_lock();
 
        /*
@@ -2343,9 +2338,8 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb,
 
        info->band = chandef->chan->band;
 
-       /* process and remove the injection radiotap header */
-       if (!ieee80211_parse_tx_radiotap(local, skb))
-               goto fail_rcu;
+       /* remove the injection radiotap header */
+       skb_pull(skb, len_rthdr);
 
        ieee80211_xmit(sdata, NULL, skb);
        rcu_read_unlock();