diff options
Diffstat (limited to 'net/wireless/scan.c')
| -rw-r--r-- | net/wireless/scan.c | 69 | 
1 files changed, 69 insertions, 0 deletions
diff --git a/net/wireless/scan.c b/net/wireless/scan.c index b5bd58d0f731..35ad69fd0838 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -57,6 +57,19 @@   * 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) @@ -137,6 +150,10 @@ static bool __cfg80211_unlink_bss(struct cfg80211_registered_device *rdev,  	list_del_init(&bss->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;  } @@ -163,6 +180,40 @@ static void __cfg80211_bss_expire(struct cfg80211_registered_device *rdev,  		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) { +		if (atomic_read(&bss->hold)) +			continue; + +		if (!list_empty(&bss->hidden_list) && +		    !bss->pub.hidden_beacon_bss) +			continue; + +		if (oldest && time_before(oldest->ts, bss->ts)) +			continue; +		oldest = bss; +	} + +	if (WARN_ON(!oldest)) +		return false; + +	/* +	 * The callers make sure to increase rdev->bss_generation if anything +	 * gets removed (and a new entry added), so there's no need to also do +	 * it here. +	 */ + +	ret = __cfg80211_unlink_bss(rdev, oldest); +	WARN_ON(!ret); +	return ret; +} +  void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev,  			   bool send_message)  { @@ -689,6 +740,7 @@ static bool cfg80211_combine_bsses(struct cfg80211_registered_device *rdev,  	const u8 *ie;  	int i, ssidlen;  	u8 fold = 0; +	u32 n_entries = 0;  	ies = rcu_access_pointer(new->pub.beacon_ies);  	if (WARN_ON(!ies)) @@ -712,6 +764,12 @@ static bool cfg80211_combine_bsses(struct cfg80211_registered_device *rdev,  	/* This is the bad part ... */  	list_for_each_entry(bss, &rdev->bss_list, list) { +		/* +		 * we're iterating all the entries anyway, so take the +		 * opportunity to validate the list length accounting +		 */ +		n_entries++; +  		if (!ether_addr_equal(bss->pub.bssid, new->pub.bssid))  			continue;  		if (bss->pub.channel != new->pub.channel) @@ -740,6 +798,10 @@ static bool cfg80211_combine_bsses(struct cfg80211_registered_device *rdev,  				   new->pub.beacon_ies);  	} +	WARN_ONCE(n_entries != rdev->bss_entries, +		  "rdev bss entries[%d]/list[len:%d] corruption\n", +		  rdev->bss_entries, n_entries); +  	return true;  } @@ -894,7 +956,14 @@ cfg80211_bss_update(struct cfg80211_registered_device *rdev,  			}  		} +		if (rdev->bss_entries >= bss_entries_limit && +		    !cfg80211_bss_expire_oldest(rdev)) { +			kfree(new); +			goto drop; +		} +  		list_add_tail(&new->list, &rdev->bss_list); +		rdev->bss_entries++;  		rb_insert_bss(rdev, new);  		found = new;  	}  |