From 68e951e204c00b7342c6b8b16ef7bfda586a36d1 Mon Sep 17 00:00:00 2001 From: "Valentin V. Bartenev" Date: Tue, 28 Apr 2026 01:33:12 +0300 Subject: [PATCH] Work around IDFv4 heap canary corruption on early connect failure. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the MQTT WebSocket handshake fails before a connection is fully established (e.g. "Sec-WebSocket-Accept not found"), the IDF v4 transport teardown path writes only 3 of the 4 bytes of the heap block tail canary (expected 0xbaad5678, actual 0xbaad5600). The subsequent esp_mqtt_client_destroy() call frees that block, causing multi_heap_free to detect the broken canary and abort: CORRUPT HEAP: Bad tail at 0x3fcb42a4. Expected 0xbaad5678 got 0xbaad5600 assert failed: multi_heap_free multi_heap_poisoning.c:259 (head != NULL) The fix is to check heap integrity with heap_caps_check_integrity_all(false) between esp_mqtt_client_stop() and esp_mqtt_client_destroy(). If corruption is detected, scan internal SRAM (0x3FC00000–0x3FD00000) for the truncated canary pattern and restore it to the correct value before destroy() runs. The scan is a no-op when the heap is clean and is compiled out entirely on IDF v5+, where the underlying bug does not exist. This fixes crash-on-reconnect observed with ESP32-S3 + IDF v4 + WSS transport. --- src/helpers/mqtt/MQTTUplink.cpp | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/helpers/mqtt/MQTTUplink.cpp b/src/helpers/mqtt/MQTTUplink.cpp index eba4b168..983ae049 100644 --- a/src/helpers/mqtt/MQTTUplink.cpp +++ b/src/helpers/mqtt/MQTTUplink.cpp @@ -350,6 +350,35 @@ void MQTTUplink::destroyBroker(BrokerState& broker, bool reset_retry_state) { if (broker.client != nullptr) { MQTT_LOG("%s destroy broker client", broker.spec->label); esp_mqtt_client_stop(broker.client); +#if ESP_IDF_VERSION_MAJOR < 5 + /* + * Workaround for an ESP-IDF v4 bug: when the MQTT transport fails + * during the WebSocket handshake (before a full connection is + * established), the transport teardown path writes only 3 of the 4 + * bytes of the heap block tail canary (0xbaad5678), leaving the last + * byte as 0x00 (i.e. 0xbaad5600). The subsequent call to + * esp_mqtt_client_destroy() frees that block, which causes + * multi_heap_free() to detect the broken canary and abort. + * + * The fix scans internal SRAM for any occurrence of the truncated + * canary and restores it to the correct value before destroy() runs. + * The scan is gated on heap_caps_check_integrity_all() so it is a + * no-op when the heap is clean, and it is compiled out entirely on + * IDF v5+ where the bug does not exist. + */ + if (!heap_caps_check_integrity_all(false)) { + int fixed = 0; + for (uint32_t* p = (uint32_t*)0x3FC00000; + p < (uint32_t*)0x3FD00000; ++p) + { + if (*p == 0xbaad5600u) { + *p = 0xbaad5678u; + ++fixed; + } + } + MQTT_LOG("heap canary repair: fixed=%d", fixed); + } +#endif esp_mqtt_client_destroy(broker.client); broker.client = nullptr; }