`iwlist scan` fails with many networks available

From: James Nylen
Date: Sun Apr 02 2017 - 17:39:48 EST


I've spent the past few days in a large hotel and noticed that `iwlist
scan` fails with the error message "Argument list too long".

This seems to be because there are a great many wireless networks in
my vicinity. See: https://bugzilla.kernel.org/show_bug.cgi?id=16384

Using `iw` as recommended in that ticket works, but it's not a great
option for me because `wicd` doesn't support it.

If I'm understanding the relevant code correctly, the attached patch
works around the issue by skipping networks that would not fit in the
scan-results buffer and returning partial results instead of just
erroring out.

This is the first time I've modified the kernel code directly. I have
a couple of questions:

- This seems to be working like I intended, but the condition was a
bit hard to reproduce consistently. Am I inadvertently breaking
things?
- Is there a better way to accomplish this? Reading the `iwlist` code
I'm not entirely sure why this is occurring, because it looks like it
should be allocating a dynamic buffer length.
diff --git a/net/wireless/scan.c b/net/wireless/scan.c
index 21be56b..fc74091 100644
--- a/net/wireless/scan.c
+++ b/net/wireless/scan.c
@@ -1699,6 +1699,7 @@ static int ieee80211_scan_results(struct cfg80211_registered_device *rdev,
struct iw_request_info *info,
char *buf, size_t len)
{
+ char *maybe_current_ev;
char *current_ev = buf;
char *end_buf = buf + len;
struct cfg80211_internal_bss *bss;
@@ -1709,14 +1710,20 @@ static int ieee80211_scan_results(struct cfg80211_registered_device *rdev,

list_for_each_entry(bss, &rdev->bss_list, list) {
if (buf + len - current_ev <= IW_EV_ADDR_LEN) {
- err = -E2BIG;
+ // Buffer too big to hold another BSS; ignore
break;
}
- current_ev = ieee80211_bss(&rdev->wiphy, info, bss,
- current_ev, end_buf);
- if (IS_ERR(current_ev)) {
- err = PTR_ERR(current_ev);
+ maybe_current_ev = ieee80211_bss(&rdev->wiphy, info, bss,
+ current_ev, end_buf);
+ if (IS_ERR(maybe_current_ev)) {
+ err = PTR_ERR(maybe_current_ev);
+ if (err == -E2BIG) {
+ // Last BSS failed to copy into buffer; ignore
+ err = 0;
+ }
break;
+ } else {
+ current_ev = maybe_current_ev;
}
}
spin_unlock_bh(&rdev->bss_lock);