]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - net/wireless/scan.c
Merge tag 'mac80211-next-for-davem-2019-04-26' of git://git.kernel.org/pub/scm/linux...
[linux.git] / net / wireless / scan.c
index 04d888628f29dcca952d38d48b785d5e2c56dfef..c04f5451f89b114cb98c43b1b3ce6724858f2a4f 100644 (file)
@@ -179,12 +179,63 @@ static bool __cfg80211_unlink_bss(struct cfg80211_registered_device *rdev,
        return true;
 }
 
+bool cfg80211_is_element_inherited(const struct element *elem,
+                                  const struct element *non_inherit_elem)
+{
+       u8 id_len, ext_id_len, i, loop_len, id;
+       const u8 *list;
+
+       if (elem->id == WLAN_EID_MULTIPLE_BSSID)
+               return false;
+
+       if (!non_inherit_elem || non_inherit_elem->datalen < 2)
+               return true;
+
+       /*
+        * non inheritance element format is:
+        * ext ID (56) | IDs list len | list | extension IDs list len | list
+        * Both lists are optional. Both lengths are mandatory.
+        * This means valid length is:
+        * elem_len = 1 (extension ID) + 2 (list len fields) + list lengths
+        */
+       id_len = non_inherit_elem->data[1];
+       if (non_inherit_elem->datalen < 3 + id_len)
+               return true;
+
+       ext_id_len = non_inherit_elem->data[2 + id_len];
+       if (non_inherit_elem->datalen < 3 + id_len + ext_id_len)
+               return true;
+
+       if (elem->id == WLAN_EID_EXTENSION) {
+               if (!ext_id_len)
+                       return true;
+               loop_len = ext_id_len;
+               list = &non_inherit_elem->data[3 + id_len];
+               id = elem->data[0];
+       } else {
+               if (!id_len)
+                       return true;
+               loop_len = id_len;
+               list = &non_inherit_elem->data[2];
+               id = elem->id;
+       }
+
+       for (i = 0; i < loop_len; i++) {
+               if (list[i] == id)
+                       return false;
+       }
+
+       return true;
+}
+EXPORT_SYMBOL(cfg80211_is_element_inherited);
+
 static size_t cfg80211_gen_new_ie(const u8 *ie, size_t ielen,
                                  const u8 *subelement, size_t subie_len,
                                  u8 *new_ie, gfp_t gfp)
 {
        u8 *pos, *tmp;
        const u8 *tmp_old, *tmp_new;
+       const struct element *non_inherit_elem;
        u8 *sub_copy;
 
        /* copy subelement as we need to change its content to
@@ -203,6 +254,11 @@ static size_t cfg80211_gen_new_ie(const u8 *ie, size_t ielen,
                pos += (tmp_new[1] + 2);
        }
 
+       /* get non inheritance list if exists */
+       non_inherit_elem =
+               cfg80211_find_ext_elem(WLAN_EID_EXT_NON_INHERITANCE,
+                                      sub_copy, subie_len);
+
        /* go through IEs in ie (skip SSID) and subelement,
         * merge them into new_ie
         */
@@ -223,8 +279,11 @@ static size_t cfg80211_gen_new_ie(const u8 *ie, size_t ielen,
                                                     subie_len);
 
                if (!tmp) {
+                       const struct element *old_elem = (void *)tmp_old;
+
                        /* ie in old ie but not in subelement */
-                       if (tmp_old[0] != WLAN_EID_MULTIPLE_BSSID) {
+                       if (cfg80211_is_element_inherited(old_elem,
+                                                         non_inherit_elem)) {
                                memcpy(pos, tmp_old, tmp_old[1] + 2);
                                pos += tmp_old[1] + 2;
                        }
@@ -268,8 +327,7 @@ static size_t cfg80211_gen_new_ie(const u8 *ie, size_t ielen,
        tmp_new = sub_copy;
        while (tmp_new + tmp_new[1] + 2 - sub_copy <= subie_len) {
                if (!(tmp_new[0] == WLAN_EID_NON_TX_BSSID_CAP ||
-                     tmp_new[0] == WLAN_EID_SSID ||
-                     tmp_new[0] == WLAN_EID_MULTI_BSSID_IDX)) {
+                     tmp_new[0] == WLAN_EID_SSID)) {
                        memcpy(pos, tmp_new, tmp_new[1] + 2);
                        pos += tmp_new[1] + 2;
                }
@@ -1397,6 +1455,78 @@ cfg80211_inform_single_bss_data(struct wiphy *wiphy,
        return &res->pub;
 }
 
+static const struct element
+*cfg80211_get_profile_continuation(const u8 *ie, size_t ielen,
+                                  const struct element *mbssid_elem,
+                                  const struct element *sub_elem)
+{
+       const u8 *mbssid_end = mbssid_elem->data + mbssid_elem->datalen;
+       const struct element *next_mbssid;
+       const struct element *next_sub;
+
+       next_mbssid = cfg80211_find_elem(WLAN_EID_MULTIPLE_BSSID,
+                                        mbssid_end,
+                                        ielen - (mbssid_end - ie));
+
+       /*
+        * If is is not the last subelement in current MBSSID IE or there isn't
+        * a next MBSSID IE - profile is complete.
+       */
+       if ((sub_elem->data + sub_elem->datalen < mbssid_end - 1) ||
+           !next_mbssid)
+               return NULL;
+
+       /* For any length error, just return NULL */
+
+       if (next_mbssid->datalen < 4)
+               return NULL;
+
+       next_sub = (void *)&next_mbssid->data[1];
+
+       if (next_mbssid->data + next_mbssid->datalen <
+           next_sub->data + next_sub->datalen)
+               return NULL;
+
+       if (next_sub->id != 0 || next_sub->datalen < 2)
+               return NULL;
+
+       /*
+        * Check if the first element in the next sub element is a start
+        * of a new profile
+        */
+       return next_sub->data[0] == WLAN_EID_NON_TX_BSSID_CAP ?
+              NULL : next_mbssid;
+}
+
+size_t cfg80211_merge_profile(const u8 *ie, size_t ielen,
+                             const struct element *mbssid_elem,
+                             const struct element *sub_elem,
+                             u8 *merged_ie, size_t max_copy_len)
+{
+       size_t copied_len = sub_elem->datalen;
+       const struct element *next_mbssid;
+
+       if (sub_elem->datalen > max_copy_len)
+               return 0;
+
+       memcpy(merged_ie, sub_elem->data, sub_elem->datalen);
+
+       while ((next_mbssid = cfg80211_get_profile_continuation(ie, ielen,
+                                                               mbssid_elem,
+                                                               sub_elem))) {
+               const struct element *next_sub = (void *)&next_mbssid->data[1];
+
+               if (copied_len + next_sub->datalen > max_copy_len)
+                       break;
+               memcpy(merged_ie + copied_len, next_sub->data,
+                      next_sub->datalen);
+               copied_len += next_sub->datalen;
+       }
+
+       return copied_len;
+}
+EXPORT_SYMBOL(cfg80211_merge_profile);
+
 static void cfg80211_parse_mbssid_data(struct wiphy *wiphy,
                                       struct cfg80211_inform_bss *data,
                                       enum cfg80211_bss_frame_type ftype,
@@ -1410,7 +1540,8 @@ static void cfg80211_parse_mbssid_data(struct wiphy *wiphy,
        const struct element *elem, *sub;
        size_t new_ie_len;
        u8 new_bssid[ETH_ALEN];
-       u8 *new_ie;
+       u8 *new_ie, *profile;
+       u64 seen_indices = 0;
        u16 capability;
        struct cfg80211_bss *bss;
 
@@ -1428,10 +1559,16 @@ static void cfg80211_parse_mbssid_data(struct wiphy *wiphy,
        if (!new_ie)
                return;
 
+       profile = kmalloc(ielen, gfp);
+       if (!profile)
+               goto out;
+
        for_each_element_id(elem, WLAN_EID_MULTIPLE_BSSID, ie, ielen) {
                if (elem->datalen < 4)
                        continue;
                for_each_element(sub, elem->data + 1, elem->datalen - 1) {
+                       u8 profile_len;
+
                        if (sub->id != 0 || sub->datalen < 4) {
                                /* not a valid BSS profile */
                                continue;
@@ -1446,16 +1583,31 @@ static void cfg80211_parse_mbssid_data(struct wiphy *wiphy,
                                continue;
                        }
 
+                       memset(profile, 0, ielen);
+                       profile_len = cfg80211_merge_profile(ie, ielen,
+                                                            elem,
+                                                            sub,
+                                                            profile,
+                                                            ielen);
+
                        /* found a Nontransmitted BSSID Profile */
                        mbssid_index_ie = cfg80211_find_ie
                                (WLAN_EID_MULTI_BSSID_IDX,
-                                sub->data, sub->datalen);
+                                profile, profile_len);
                        if (!mbssid_index_ie || mbssid_index_ie[1] < 1 ||
-                           mbssid_index_ie[2] == 0) {
+                           mbssid_index_ie[2] == 0 ||
+                           mbssid_index_ie[2] > 46) {
                                /* No valid Multiple BSSID-Index element */
                                continue;
                        }
 
+                       if (seen_indices & BIT(mbssid_index_ie[2]))
+                               /* We don't support legacy split of a profile */
+                               net_dbg_ratelimited("Partial info for BSSID index %d\n",
+                                                   mbssid_index_ie[2]);
+
+                       seen_indices |= BIT(mbssid_index_ie[2]);
+
                        non_tx_data->bssid_index = mbssid_index_ie[2];
                        non_tx_data->max_bssid_indicator = elem->data[0];
 
@@ -1464,13 +1616,14 @@ static void cfg80211_parse_mbssid_data(struct wiphy *wiphy,
                                               non_tx_data->bssid_index,
                                               new_bssid);
                        memset(new_ie, 0, IEEE80211_MAX_DATA_LEN);
-                       new_ie_len = cfg80211_gen_new_ie(ie, ielen, sub->data,
-                                                        sub->datalen, new_ie,
+                       new_ie_len = cfg80211_gen_new_ie(ie, ielen,
+                                                        profile,
+                                                        profile_len, new_ie,
                                                         gfp);
                        if (!new_ie_len)
                                continue;
 
-                       capability = get_unaligned_le16(sub->data + 2);
+                       capability = get_unaligned_le16(profile + 2);
                        bss = cfg80211_inform_single_bss_data(wiphy, data,
                                                              ftype,
                                                              new_bssid, tsf,
@@ -1486,7 +1639,9 @@ static void cfg80211_parse_mbssid_data(struct wiphy *wiphy,
                }
        }
 
+out:
        kfree(new_ie);
+       kfree(profile);
 }
 
 struct cfg80211_bss *