|
@@ -0,0 +1,150 @@
|
|
|
|
|
+From: Johannes Berg <[email protected]>
|
|
|
|
|
+Date: Tue, 15 Nov 2016 12:05:11 +0100
|
|
|
|
|
+Subject: [PATCH] cfg80211: limit scan results cache size
|
|
|
|
|
+
|
|
|
|
|
+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.
|
|
|
|
|
+
|
|
|
|
|
+Cc: [email protected]
|
|
|
|
|
+Signed-off-by: Johannes Berg <[email protected]>
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+--- a/net/wireless/core.h
|
|
|
|
|
++++ b/net/wireless/core.h
|
|
|
|
|
+@@ -71,6 +71,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
|
|
|
|
|
+@@ -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
|
|
|
|
|
+
|
|
|
|
|
+ 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
|
|
|
|
|
+ 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(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))
|
|
|
|
|
+@@ -712,6 +764,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)
|
|
|
|
|
+@@ -740,6 +798,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;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+@@ -894,7 +956,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;
|
|
|
|
|
+ }
|