You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Bug report for Bruce maintainers — Beacon/AP spam broken since 1.15; root cause is -flto
This file is a deliverable (a bug-report write-up), not an implementation plan.
The fix is already applied and confirmed in the downstream fork; nothing needs to
be implemented here. The text below is meant to be sent to the upstream developers
(relates to issue #2608).
TL;DR
Beacon / AP spam (WiFi → Beacon Spam) injects frames that a monitor-mode sniffer
can see, but phones and laptops never list the spoofed SSIDs. It works on 1.14
and is broken on 1.15 / beta / current dev. The beacon code is byte-for-byte
identical between 1.14 and dev, so it is not a beacon-code bug. The regression
is caused by the -flto build flag that dev added after 1.14 (1.14 does not use
LTO). Disabling -flto fixes it with zero source changes. Deterministic.
Beacon spam not working #2608 reports it firmware-wide (also LilyGo T-Embed CC1101 Plus). A build-flag root
cause is consistent with "affects every board".
WiFi → Beacon Spam (Random / Funny / Rickroll / Custom): the device transmits the
beacons (visible on a monitor-mode capture), but no client lists the spoofed APs.
Key tell: it works while a USB serial monitor is attached and reading CDC;
it fails standalone — on battery, and even on a plain USB charger with no data
host. (This is what sent us down the wrong path for a long time.)
What it is NOT
Not the frame contents.prepareBeaconPacket() and the template are unchanged
from 1.14; the emitted frame is a well-formed WPA2 beacon.
Not the WiFi setup. During the failure: WiFi.getMode() == WIFI_MODE_APSTA,
the WIFI_ATK_NAME softAP is up, and esp_wifi_get_channel() matches the requested
hopping channel.
Not power. Fails on a stable 5 V USB charger (no host) exactly like on battery.
Not modem sleep.esp_wifi_set_ps(WIFI_PS_NONE) did not help.
Not ESP_ERR_NO_MEM per se — that is a downstream symptom (see below).
Diagnostic evidence
We instrumented beaconSpamList() / beaconSpamSingle() to log, once per second, the esp_wifi_80211_tx() return code, requested vs actual channel, WiFi mode and softAP
SSID.
Working case (serial host attached), typical line: mode=3 reqCh=5 actCh=5 txOK≈600/s txFAIL≈15–25% lastErr=0x101(ESP_ERR_NO_MEM) apSSID=BruceAttack
→ APs appear on the phone.
ESP_ERR_NO_MEM (WiFi TX buffer pool momentarily exhausted under fast injection) is
present at the same rate in both the working and failing cases, so it is not
the discriminator. It is a consequence of the driver not draining its TX queue, not
the cause of client-side invisibility.
Decisive observation: adding a once-per-second on-screen status draw (an
SPI/TFT transaction inside the injection loop) made it work standalone; removing
it broke it again. Symmetrically, the serial logging's once-per-second printf (a
blocking USB-CDC write when a host is attached) is exactly what made the
"works with serial" case work. ⇒ the injection loop needs a periodic blocking
pause for the queued frames to actually be aired.
Dead end worth documenting: retrying esp_wifi_80211_tx() on ESP_ERR_NO_MEM
made it strictly worse — a 1 ms yield per congested frame slows the per-SSID
cycle so much that clients (which dwell only ~100–300 ms per channel during a passive
scan) catch too few SSIDs to list any. Do not "fix" the NO_MEM by retrying.
Root cause: -flto
The only relevant build difference between 1.14 and dev is that dev's platformio.ini adds -flto (plus -ffunction-sections / -fdata-sections); 1.14 does not use LTO.
Bisect result: with the beacon code left byte-identical to 1.14 and -flto
disabled, beacon spam works standalone (battery and USB charger, no serial host,
no on-screen "crutch"). Re-enabling -flto reproduces the failure. This is
deterministic and requires no source changes.
Mechanism (hypothesis — the bisect result above is certain, this part is inferred)
With LTO, the src/-side injection loop is inlined/optimized aggressively enough that
the Arduino loopTask (core 1) starves the WiFi driver task: the driver never gets a
window to push the queued management frames onto the air. The existing per-packet vTaskDelay(1) is evidently insufficient under LTO. Any coarser periodic pause — a
blocking USB-CDC write, or an SPI display update — yields long enough for the driver to
flush its TX queue, which is why both "crutches" and an attached serial monitor masked
the bug. Without LTO the loop's timing matches 1.14 and the frames are aired normally.
Fix used downstream
Disable -flto in platformio.ini; beacon code unchanged from 1.14.
Cost: firmware grows from ~81 % → ~91 % of the 8 MB OTA partition on m5stack-sticks3
(LTO was buying ~10 % flash). RAM unaffected.
Suggested upstream fix (better than dropping LTO globally)
Since dropping LTO globally costs flash (and a little perf), a targeted fix is likely
preferable. Options, in rough order of preference:
Add an explicit, LTO-proof pace/yield in the beacon injection loop — a small
periodic vTaskDelay/taskYIELD at a coarse cadence (once per SSID-list pass,
or every N frames), not per-frame and not gated on NO_MEM (per-frame
pacing slows the SSID cycle and hides the SSIDs from scanners — see the dead end
above).
Ensure the WiFi driver/TX task gets CPU (task priority / core placement) so an
LTO-tightened loop can't starve it.
Keep LTO globally but exclude the injection code — either an optimize/noinline
attribute on the beacon-inject function(s), or disable LTO for the wifi_atks
translation unit only.
Reproduction
Build dev (or use an affected 1.15/beta release) for m5stack-sticks3.
Flash, then unplug from any computer — run on battery or a dumb USB charger
(no serial host).
WiFi → Beacon Spam → Random. Scan on a phone: no spoofed APs (a monitor-mode
sniffer still shows the frames on the air).
Rebuild with -flto removed from platformio.inibuild_flags; reflash; repeat
2–3 → the spoofed APs now appear. No source changes required.
Toolchain (identical for 1.14 source and dev — so this is not a framework-version
difference): pioarduino platform-espressif32 55.03.36 (Arduino 3.3.6 / IDF 5.5),
framework libs bruce_esp32-arduino-libs-20260123.
Bug report for Bruce maintainers — Beacon/AP spam broken since 1.15; root cause is
-fltoTL;DR
Beacon / AP spam (WiFi → Beacon Spam) injects frames that a monitor-mode sniffer
can see, but phones and laptops never list the spoofed SSIDs. It works on 1.14
and is broken on 1.15 / beta / current dev. The beacon code is byte-for-byte
identical between 1.14 and dev, so it is not a beacon-code bug. The regression
is caused by the
-fltobuild flag that dev added after 1.14 (1.14 does not useLTO). Disabling
-fltofixes it with zero source changes. Deterministic.Relates to: #2608
Affected
m5stack-sticks3, ESP32-S3, 8 MB, OPI PSRAM).cause is consistent with "affects every board".
Symptom
beacons (visible on a monitor-mode capture), but no client lists the spoofed APs.
it fails standalone — on battery, and even on a plain USB charger with no data
host. (This is what sent us down the wrong path for a long time.)
What it is NOT
prepareBeaconPacket()and the template are unchangedfrom 1.14; the emitted frame is a well-formed WPA2 beacon.
WiFi.getMode() == WIFI_MODE_APSTA,the
WIFI_ATK_NAMEsoftAP is up, andesp_wifi_get_channel()matches the requestedhopping channel.
esp_wifi_set_ps(WIFI_PS_NONE)did not help.ESP_ERR_NO_MEMper se — that is a downstream symptom (see below).Diagnostic evidence
We instrumented
beaconSpamList()/beaconSpamSingle()to log, once per second, theesp_wifi_80211_tx()return code, requested vs actual channel, WiFi mode and softAPSSID.
mode=3 reqCh=5 actCh=5 txOK≈600/s txFAIL≈15–25% lastErr=0x101(ESP_ERR_NO_MEM) apSSID=BruceAttack→ APs appear on the phone.
ESP_ERR_NO_MEM(WiFi TX buffer pool momentarily exhausted under fast injection) ispresent at the same rate in both the working and failing cases, so it is not
the discriminator. It is a consequence of the driver not draining its TX queue, not
the cause of client-side invisibility.
SPI/TFT transaction inside the injection loop) made it work standalone; removing
it broke it again. Symmetrically, the serial logging's once-per-second
printf(ablocking USB-CDC write when a host is attached) is exactly what made the
"works with serial" case work. ⇒ the injection loop needs a periodic blocking
pause for the queued frames to actually be aired.
esp_wifi_80211_tx()onESP_ERR_NO_MEMmade it strictly worse — a 1 ms yield per congested frame slows the per-SSID
cycle so much that clients (which dwell only ~100–300 ms per channel during a passive
scan) catch too few SSIDs to list any. Do not "fix" the
NO_MEMby retrying.Root cause:
-fltoplatformio.iniadds-flto(plus-ffunction-sections/-fdata-sections);1.14 does not use LTO.
-fltodisabled, beacon spam works standalone (battery and USB charger, no serial host,
no on-screen "crutch"). Re-enabling
-fltoreproduces the failure. This isdeterministic and requires no source changes.
Mechanism (hypothesis — the bisect result above is certain, this part is inferred)
With LTO, the
src/-side injection loop is inlined/optimized aggressively enough thatthe Arduino
loopTask(core 1) starves the WiFi driver task: the driver never gets awindow to push the queued management frames onto the air. The existing per-packet
vTaskDelay(1)is evidently insufficient under LTO. Any coarser periodic pause — ablocking USB-CDC write, or an SPI display update — yields long enough for the driver to
flush its TX queue, which is why both "crutches" and an attached serial monitor masked
the bug. Without LTO the loop's timing matches 1.14 and the frames are aired normally.
Fix used downstream
Disable
-fltoinplatformio.ini; beacon code unchanged from 1.14.Cost: firmware grows from ~81 % → ~91 % of the 8 MB OTA partition on
m5stack-sticks3(LTO was buying ~10 % flash). RAM unaffected.
Suggested upstream fix (better than dropping LTO globally)
Since dropping LTO globally costs flash (and a little perf), a targeted fix is likely
preferable. Options, in rough order of preference:
periodic
vTaskDelay/taskYIELDat a coarse cadence (once per SSID-list pass,or every N frames), not per-frame and not gated on
NO_MEM(per-framepacing slows the SSID cycle and hides the SSIDs from scanners — see the dead end
above).
LTO-tightened loop can't starve it.
optimize/noinlineattribute on the beacon-inject function(s), or disable LTO for the
wifi_atkstranslation unit only.
Reproduction
m5stack-sticks3.(no serial host).
sniffer still shows the frames on the air).
-fltoremoved fromplatformio.inibuild_flags; reflash; repeat2–3 → the spoofed APs now appear. No source changes required.
Environment
m5stack-sticks3(ESP32-S3, 8 MB, OPI PSRAM),ARDUINO_RUNNING_CORE=1,CONFIG_ASYNC_TCP_RUNNING_CORE=1, FreeRTOS HZ 1000.difference): pioarduino
platform-espressif3255.03.36 (Arduino 3.3.6 / IDF 5.5),framework libs
bruce_esp32-arduino-libs-20260123.