| From 9853a55ef1bb66d7411136046060bbfb69c714fa Mon Sep 17 00:00:00 2001 |
| From: Johannes Berg <johannes.berg@intel.com> |
| Date: Tue, 15 Nov 2016 12:05:11 +0100 |
| Subject: cfg80211: limit scan results cache size |
| |
| From: Johannes Berg <johannes.berg@intel.com> |
| |
| commit 9853a55ef1bb66d7411136046060bbfb69c714fa upstream. |
| |
| It's possible to make scanning consume almost arbitrary amounts |
| of memory, e.g. by sending beacon frames with random BSSIDs at |
| high rates while somebody is scanning. |
| |
| Limit the number of BSS table entries we're willing to cache to |
| 1000, limiting maximum memory usage to maybe 4-5MB, but lower |
| in practice - that would be the case for having both full-sized |
| beacon and probe response frames for each entry; this seems not |
| possible in practice, so a limit of 1000 entries will likely be |
| closer to 0.5 MB. |
| |
| Signed-off-by: Johannes Berg <johannes.berg@intel.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| net/wireless/core.h | 1 |
| net/wireless/scan.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| 2 files changed, 70 insertions(+) |
| |
| --- a/net/wireless/core.h |
| +++ b/net/wireless/core.h |
| @@ -72,6 +72,7 @@ struct cfg80211_registered_device { |
| struct list_head bss_list; |
| struct rb_root bss_tree; |
| u32 bss_generation; |
| + u32 bss_entries; |
| struct cfg80211_scan_request *scan_req; /* protected by RTNL */ |
| struct sk_buff *scan_msg; |
| struct cfg80211_sched_scan_request __rcu *sched_scan_req; |
| --- a/net/wireless/scan.c |
| +++ b/net/wireless/scan.c |
| @@ -56,6 +56,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) |
| @@ -136,6 +149,10 @@ static bool __cfg80211_unlink_bss(struct |
| |
| 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; |
| } |
| @@ -162,6 +179,40 @@ static void __cfg80211_bss_expire(struct |
| 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) |
| { |
| @@ -687,6 +738,7 @@ static bool cfg80211_combine_bsses(struc |
| 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)) |
| @@ -710,6 +762,12 @@ static bool cfg80211_combine_bsses(struc |
| /* 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) |
| @@ -738,6 +796,10 @@ static bool cfg80211_combine_bsses(struc |
| 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; |
| } |
| |
| @@ -890,7 +952,14 @@ cfg80211_bss_update(struct cfg80211_regi |
| } |
| } |
| |
| + 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; |
| } |