wifi: cfg80211: rewrite merging of inherited elements

The cfg80211_gen_new_ie function merges the IEs using inheritance rules.
Rewrite this function to fix issues around inheritance rules. In
particular, vendor elements do not require any special handling, as they
are either all inherited or overridden by the subprofile.
Also, add fragmentation handling as this may be needed in some cases.

This also changes the function to not require making a copy. The new
version could be optimized a bit by explicitly tracking which IEs have
been handled already rather than looking that up again every time.

Note that a small behavioural change is the removal of the SSID special
handling. This should be fine for the MBSSID element, as the SSID must
be included in the subelement.

Fixes: 0b8fb8235b ("cfg80211: Parsing of Multiple BSSID information in scanning")
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Gregory Greenman <gregory.greenman@intel.com>
Link: https://lore.kernel.org/r/20230616094949.bc6152e146db.I2b5f3bc45085e1901e5b5192a674436adaf94748@changeid
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
This commit is contained in:
Benjamin Berg 2023-06-16 09:54:03 +03:00 committed by Johannes Berg
parent 03e7e493f1
commit dfd9aa3e7a

View File

@ -259,117 +259,152 @@ bool cfg80211_is_element_inherited(const struct element *elem,
} }
EXPORT_SYMBOL(cfg80211_is_element_inherited); EXPORT_SYMBOL(cfg80211_is_element_inherited);
static size_t cfg80211_gen_new_ie(const u8 *ie, size_t ielen, static size_t cfg80211_copy_elem_with_frags(const struct element *elem,
const u8 *subelement, size_t subie_len, const u8 *ie, size_t ie_len,
u8 *new_ie, gfp_t gfp) u8 **pos, u8 *buf, size_t buf_len)
{ {
u8 *pos, *tmp; if (WARN_ON((u8 *)elem < ie || elem->data > ie + ie_len ||
const u8 *tmp_old, *tmp_new; elem->data + elem->datalen > ie + ie_len))
const struct element *non_inherit_elem;
u8 *sub_copy;
/* copy subelement as we need to change its content to
* mark an ie after it is processed.
*/
sub_copy = kmemdup(subelement, subie_len, gfp);
if (!sub_copy)
return 0; return 0;
pos = &new_ie[0]; if (elem->datalen + 2 > buf + buf_len - *pos)
return 0;
/* set new ssid */ memcpy(*pos, elem, elem->datalen + 2);
tmp_new = cfg80211_find_ie(WLAN_EID_SSID, sub_copy, subie_len); *pos += elem->datalen + 2;
if (tmp_new) {
memcpy(pos, tmp_new, tmp_new[1] + 2); /* Finish if it is not fragmented */
pos += (tmp_new[1] + 2); if (elem->datalen != 255)
return *pos - buf;
ie_len = ie + ie_len - elem->data - elem->datalen;
ie = (const u8 *)elem->data + elem->datalen;
for_each_element(elem, ie, ie_len) {
if (elem->id != WLAN_EID_FRAGMENT)
break;
if (elem->datalen + 2 > buf + buf_len - *pos)
return 0;
memcpy(*pos, elem, elem->datalen + 2);
*pos += elem->datalen + 2;
if (elem->datalen != 255)
break;
} }
/* get non inheritance list if exists */ return *pos - buf;
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, static size_t cfg80211_gen_new_ie(const u8 *ie, size_t ielen,
* merge them into new_ie const u8 *subie, size_t subie_len,
u8 *new_ie, size_t new_ie_len)
{
const struct element *non_inherit_elem, *parent, *sub;
u8 *pos = new_ie;
u8 id, ext_id;
unsigned int match_len;
non_inherit_elem = cfg80211_find_ext_elem(WLAN_EID_EXT_NON_INHERITANCE,
subie, subie_len);
/* We copy the elements one by one from the parent to the generated
* elements.
* If they are not inherited (included in subie or in the non
* inheritance element), then we copy all occurrences the first time
* we see this element type.
*/ */
tmp_old = cfg80211_find_ie(WLAN_EID_SSID, ie, ielen); for_each_element(parent, ie, ielen) {
tmp_old = (tmp_old) ? tmp_old + tmp_old[1] + 2 : ie; if (parent->id == WLAN_EID_FRAGMENT)
continue;
if (parent->id == WLAN_EID_EXTENSION) {
if (parent->datalen < 1)
continue;
id = WLAN_EID_EXTENSION;
ext_id = parent->data[0];
match_len = 1;
} else {
id = parent->id;
match_len = 0;
}
/* Find first occurrence in subie */
sub = cfg80211_find_elem_match(id, subie, subie_len,
&ext_id, match_len, 0);
/* Copy from parent if not in subie and inherited */
if (!sub &&
cfg80211_is_element_inherited(parent, non_inherit_elem)) {
if (!cfg80211_copy_elem_with_frags(parent,
ie, ielen,
&pos, new_ie,
new_ie_len))
return 0;
while (tmp_old + 2 - ie <= ielen &&
tmp_old + tmp_old[1] + 2 - ie <= ielen) {
if (tmp_old[0] == 0) {
tmp_old++;
continue; continue;
} }
if (tmp_old[0] == WLAN_EID_EXTENSION) /* Already copied if an earlier element had the same type */
tmp = (u8 *)cfg80211_find_ext_ie(tmp_old[2], sub_copy, if (cfg80211_find_elem_match(id, ie, (u8 *)parent - ie,
subie_len); &ext_id, match_len, 0))
else continue;
tmp = (u8 *)cfg80211_find_ie(tmp_old[0], sub_copy,
subie_len);
if (!tmp) { /* Not inheriting, copy all similar elements from subie */
const struct element *old_elem = (void *)tmp_old; while (sub) {
if (!cfg80211_copy_elem_with_frags(sub,
subie, subie_len,
&pos, new_ie,
new_ie_len))
return 0;
/* ie in old ie but not in subelement */ sub = cfg80211_find_elem_match(id,
if (cfg80211_is_element_inherited(old_elem, sub->data + sub->datalen,
non_inherit_elem)) { subie_len + subie -
memcpy(pos, tmp_old, tmp_old[1] + 2); (sub->data +
pos += tmp_old[1] + 2; sub->datalen),
} &ext_id, match_len, 0);
} else {
/* ie in transmitting ie also in subelement,
* copy from subelement and flag the ie in subelement
* as copied (by setting eid field to WLAN_EID_SSID,
* which is skipped anyway).
* For vendor ie, compare OUI + type + subType to
* determine if they are the same ie.
*/
if (tmp_old[0] == WLAN_EID_VENDOR_SPECIFIC) {
if (tmp_old[1] >= 5 && tmp[1] >= 5 &&
!memcmp(tmp_old + 2, tmp + 2, 5)) {
/* same vendor ie, copy from
* subelement
*/
memcpy(pos, tmp, tmp[1] + 2);
pos += tmp[1] + 2;
tmp[0] = WLAN_EID_SSID;
} else {
memcpy(pos, tmp_old, tmp_old[1] + 2);
pos += tmp_old[1] + 2;
}
} else {
/* copy ie from subelement into new ie */
memcpy(pos, tmp, tmp[1] + 2);
pos += tmp[1] + 2;
tmp[0] = WLAN_EID_SSID;
}
} }
if (tmp_old + tmp_old[1] + 2 - ie == ielen)
break;
tmp_old += tmp_old[1] + 2;
} }
/* go through subelement again to check if there is any ie not /* The above misses elements that are included in subie but not in the
* copied to new ie, skip ssid, capability, bssid-index ie * parent, so do a pass over subie and append those.
* Skip the non-tx BSSID caps and non-inheritance element.
*/ */
tmp_new = sub_copy; for_each_element(sub, subie, subie_len) {
while (tmp_new + 2 - sub_copy <= subie_len && if (sub->id == WLAN_EID_NON_TX_BSSID_CAP)
tmp_new + tmp_new[1] + 2 - sub_copy <= subie_len) { continue;
if (!(tmp_new[0] == WLAN_EID_NON_TX_BSSID_CAP ||
tmp_new[0] == WLAN_EID_SSID)) { if (sub->id == WLAN_EID_FRAGMENT)
memcpy(pos, tmp_new, tmp_new[1] + 2); continue;
pos += tmp_new[1] + 2;
if (sub->id == WLAN_EID_EXTENSION) {
if (sub->datalen < 1)
continue;
id = WLAN_EID_EXTENSION;
ext_id = sub->data[0];
match_len = 1;
if (ext_id == WLAN_EID_EXT_NON_INHERITANCE)
continue;
} else {
id = sub->id;
match_len = 0;
} }
if (tmp_new + tmp_new[1] + 2 - sub_copy == subie_len)
break; /* Processed if one was included in the parent */
tmp_new += tmp_new[1] + 2; if (cfg80211_find_elem_match(id, ie, ielen,
&ext_id, match_len, 0))
continue;
if (!cfg80211_copy_elem_with_frags(sub, subie, subie_len,
&pos, new_ie, new_ie_len))
return 0;
} }
kfree(sub_copy);
return pos - new_ie; return pos - new_ie;
} }
@ -2228,7 +2263,7 @@ static void cfg80211_parse_mbssid_data(struct wiphy *wiphy,
new_ie_len = cfg80211_gen_new_ie(ie, ielen, new_ie_len = cfg80211_gen_new_ie(ie, ielen,
profile, profile,
profile_len, new_ie, profile_len, new_ie,
gfp); IEEE80211_MAX_DATA_LEN);
if (!new_ie_len) if (!new_ie_len)
continue; continue;