4054 lines · 105.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * cfg80211 scan result handling * * Copyright 2008 Johannes Berg <johannes@sipsolutions.net> * Copyright 2013-2014  Intel Mobile Communications GmbH * Copyright 2016	Intel Deutschland GmbH * Copyright (C) 2018-2026 Intel Corporation */#include <linux/kernel.h>#include <linux/slab.h>#include <linux/module.h>#include <linux/netdevice.h>#include <linux/wireless.h>#include <linux/nl80211.h>#include <linux/etherdevice.h>#include <linux/crc32.h>#include <linux/bitfield.h>#include <net/arp.h>#include <net/cfg80211.h>#include <net/cfg80211-wext.h>#include <net/iw_handler.h>#include <kunit/visibility.h>#include "core.h"#include "nl80211.h"#include "wext-compat.h"#include "rdev-ops.h" /** * DOC: BSS tree/list structure * * At the top level, the BSS list is kept in both a list in each * registered device (@bss_list) as well as an RB-tree for faster * lookup. In the RB-tree, entries can be looked up using their * channel, MESHID, MESHCONF (for MBSSes) or channel, BSSID, SSID * for other BSSes. * * Due to the possibility of hidden SSIDs, there's a second level * structure, the "hidden_list" and "hidden_beacon_bss" pointer. * The hidden_list connects all BSSes belonging to a single AP * that has a hidden SSID, and connects beacon and probe response * entries. For a probe response entry for a hidden SSID, the * hidden_beacon_bss pointer points to the BSS struct holding the * beacon's information. * * Reference counting is done for all these references except for * the hidden_list, so that a beacon BSS struct that is otherwise * not referenced has one reference for being on the bss_list and * one for each probe response entry that points to it using the * hidden_beacon_bss pointer. When a BSS struct that has such a * pointer is get/put, the refcount update is also propagated to * the referenced struct, this ensure that it cannot get removed * while somebody is using the probe response version. * * Note that the hidden_beacon_bss pointer never changes, due to * the reference counting. Therefore, no locking is needed for * it. * * Also note that the hidden_beacon_bss pointer is only relevant * if the driver uses something other than the IEs, e.g. private * data stored in the BSS struct, since the beacon IEs are * also linked into the probe response struct. */ /* * Limit the number of BSS entries stored in mac80211. Each one is * a bit over 4k at most, so this limits to roughly 4-5M of memory. * If somebody wants to really attack this though, they'd likely * use small beacons, and only one type of frame, limiting each of * the entries to a much smaller size (in order to generate more * entries in total, so overhead is bigger.) */static int bss_entries_limit = 1000;module_param(bss_entries_limit, int, 0644);MODULE_PARM_DESC(bss_entries_limit,                 "limit to number of scan BSS entries (per wiphy, default 1000)"); #define IEEE80211_SCAN_RESULT_EXPIRE	(30 * HZ) static void bss_free(struct cfg80211_internal_bss *bss){	struct cfg80211_bss_ies *ies; 	if (WARN_ON(atomic_read(&bss->hold)))		return; 	ies = (void *)rcu_access_pointer(bss->pub.beacon_ies);	if (ies && !bss->pub.hidden_beacon_bss)		kfree_rcu(ies, rcu_head);	ies = (void *)rcu_access_pointer(bss->pub.proberesp_ies);	if (ies)		kfree_rcu(ies, rcu_head); 	/*	 * This happens when the module is removed, it doesn't	 * really matter any more save for completeness	 */	if (!list_empty(&bss->hidden_list))		list_del(&bss->hidden_list); 	kfree(bss);} static inline void bss_ref_get(struct cfg80211_registered_device *rdev,			       struct cfg80211_internal_bss *bss){	lockdep_assert_held(&rdev->bss_lock); 	bss->refcount++; 	if (bss->pub.hidden_beacon_bss)		bss_from_pub(bss->pub.hidden_beacon_bss)->refcount++; 	if (bss->pub.transmitted_bss)		bss_from_pub(bss->pub.transmitted_bss)->refcount++;} static inline void bss_ref_put(struct cfg80211_registered_device *rdev,			       struct cfg80211_internal_bss *bss){	lockdep_assert_held(&rdev->bss_lock); 	if (bss->pub.hidden_beacon_bss) {		struct cfg80211_internal_bss *hbss; 		hbss = bss_from_pub(bss->pub.hidden_beacon_bss);		hbss->refcount--;		if (hbss->refcount == 0)			bss_free(hbss);	} 	if (bss->pub.transmitted_bss) {		struct cfg80211_internal_bss *tbss; 		tbss = bss_from_pub(bss->pub.transmitted_bss);		tbss->refcount--;		if (tbss->refcount == 0)			bss_free(tbss);	} 	bss->refcount--;	if (bss->refcount == 0)		bss_free(bss);} static bool __cfg80211_unlink_bss(struct cfg80211_registered_device *rdev,				  struct cfg80211_internal_bss *bss){	lockdep_assert_held(&rdev->bss_lock); 	if (!list_empty(&bss->hidden_list)) {		/*		 * don't remove the beacon entry if it has		 * probe responses associated with it		 */		if (!bss->pub.hidden_beacon_bss)			return false;		/*		 * if it's a probe response entry break its		 * link to the other entries in the group		 */		list_del_init(&bss->hidden_list);	} 	list_del_init(&bss->list);	list_del_init(&bss->pub.nontrans_list);	rb_erase(&bss->rbn, &rdev->bss_tree);	rdev->bss_entries--;	WARN_ONCE((rdev->bss_entries == 0) ^ list_empty(&rdev->bss_list),		  "rdev bss entries[%d]/list[empty:%d] corruption\n",		  rdev->bss_entries, list_empty(&rdev->bss_list));	bss_ref_put(rdev, bss);	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 (elem->id == WLAN_EID_EXTENSION && elem->datalen > 1 &&	    elem->data[0] == WLAN_EID_EXT_EHT_MULTI_LINK)		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 || !elem->datalen)			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_copy_elem_with_frags(const struct element *elem,					    const u8 *ie, size_t ie_len,					    u8 **pos, u8 *buf, size_t buf_len){	if (WARN_ON((u8 *)elem < ie || elem->data > ie + ie_len ||		    elem->data + elem->datalen > ie + ie_len))		return 0; 	if (elem->datalen + 2 > buf + buf_len - *pos)		return 0; 	memcpy(*pos, elem, elem->datalen + 2);	*pos += elem->datalen + 2; 	/* Finish if it is not fragmented  */	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;	} 	return *pos - buf;} VISIBLE_IF_CFG80211_KUNIT size_tcfg80211_gen_new_ie(const u8 *ie, size_t ielen,		    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;	const u8 *mbssid_index_ie;	u8 id, ext_id, bssid_index = 255;	unsigned int match_len; 	non_inherit_elem = cfg80211_find_ext_elem(WLAN_EID_EXT_NON_INHERITANCE,						  subie, subie_len); 	mbssid_index_ie = cfg80211_find_ie(WLAN_EID_MULTI_BSSID_IDX, subie,					   subie_len);	if (mbssid_index_ie && mbssid_index_ie[1] > 0 &&	    mbssid_index_ie[2] > 0 && mbssid_index_ie[2] <= 46)		bssid_index = mbssid_index_ie[2]; 	/* 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.	 */	for_each_element(parent, ie, ielen) {		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; 			continue;		} 		/* For ML probe response, match the MLE in the frame body with		 * MLD id being 'bssid_index'		 */		if (parent->id == WLAN_EID_EXTENSION &&		    parent->data[0] == WLAN_EID_EXT_EHT_MULTI_LINK &&		    ieee80211_mle_type_ok(parent->data + 1,					  IEEE80211_ML_CONTROL_TYPE_BASIC,					  parent->datalen - 1) &&		    bssid_index == ieee80211_mle_get_mld_id(parent->data + 1)) {			if (!cfg80211_copy_elem_with_frags(parent,							   ie, ielen,							   &pos, new_ie,							   new_ie_len))				return 0; 			/* Continue here to prevent processing the MLE in			 * sub-element, which AP MLD should not carry			 */			continue;		} 		/* Already copied if an earlier element had the same type */		if (cfg80211_find_elem_match(id, ie, (u8 *)parent - ie,					     &ext_id, match_len, 0))			continue; 		/* Not inheriting, copy all similar elements from subie */		while (sub) {			if (!cfg80211_copy_elem_with_frags(sub,							   subie, subie_len,							   &pos, new_ie,							   new_ie_len))				return 0; 			sub = cfg80211_find_elem_match(id,						       sub->data + sub->datalen,						       subie_len + subie -						       (sub->data +							sub->datalen),						       &ext_id, match_len, 0);		}	} 	/* The above misses elements that are included in subie but not in the	 * parent, so do a pass over subie and append those.	 * Skip the non-tx BSSID caps and non-inheritance element.	 */	for_each_element(sub, subie, subie_len) {		if (sub->id == WLAN_EID_NON_TX_BSSID_CAP)			continue; 		if (sub->id == WLAN_EID_FRAGMENT)			continue; 		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;		} 		/* Processed if one was included in the parent */		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;	} 	return pos - new_ie;}EXPORT_SYMBOL_IF_CFG80211_KUNIT(cfg80211_gen_new_ie); static bool is_bss(struct cfg80211_bss *a, const u8 *bssid,		   const u8 *ssid, size_t ssid_len){	const struct cfg80211_bss_ies *ies;	const struct element *ssid_elem; 	if (bssid && !ether_addr_equal(a->bssid, bssid))		return false; 	if (!ssid)		return true; 	ies = rcu_access_pointer(a->ies);	if (!ies)		return false;	ssid_elem = cfg80211_find_elem(WLAN_EID_SSID, ies->data, ies->len);	if (!ssid_elem)		return false;	if (ssid_elem->datalen != ssid_len)		return false;	return memcmp(ssid_elem->data, ssid, ssid_len) == 0;} static intcfg80211_add_nontrans_list(struct cfg80211_bss *trans_bss,			   struct cfg80211_bss *nontrans_bss){	const struct element *ssid_elem;	struct cfg80211_bss *bss = NULL; 	rcu_read_lock();	ssid_elem = ieee80211_bss_get_elem(nontrans_bss, WLAN_EID_SSID);	if (!ssid_elem) {		rcu_read_unlock();		return -EINVAL;	} 	/* check if nontrans_bss is in the list */	list_for_each_entry(bss, &trans_bss->nontrans_list, nontrans_list) {		if (is_bss(bss, nontrans_bss->bssid, ssid_elem->data,			   ssid_elem->datalen)) {			rcu_read_unlock();			return 0;		}	} 	rcu_read_unlock(); 	/*	 * This is a bit weird - it's not on the list, but already on another	 * one! The only way that could happen is if there's some BSSID/SSID	 * shared by multiple APs in their multi-BSSID profiles, potentially	 * with hidden SSID mixed in ... ignore it.	 */	if (!list_empty(&nontrans_bss->nontrans_list))		return -EINVAL; 	/* add to the list */	list_add_tail(&nontrans_bss->nontrans_list, &trans_bss->nontrans_list);	return 0;} static void __cfg80211_bss_expire(struct cfg80211_registered_device *rdev,				  unsigned long expire_time){	struct cfg80211_internal_bss *bss, *tmp;	bool expired = false; 	lockdep_assert_held(&rdev->bss_lock); 	list_for_each_entry_safe(bss, tmp, &rdev->bss_list, list) {		if (atomic_read(&bss->hold))			continue;		if (!time_after(expire_time, bss->ts))			continue; 		if (__cfg80211_unlink_bss(rdev, bss))			expired = true;	} 	if (expired)		rdev->bss_generation++;} static bool cfg80211_bss_expire_oldest(struct cfg80211_registered_device *rdev){	struct cfg80211_internal_bss *bss, *oldest = NULL;	bool ret; 	lockdep_assert_held(&rdev->bss_lock); 	list_for_each_entry(bss, &rdev->bss_list, list) {

Lines 1–500 of 4054.