Explorar o código

Work around IDFv4 heap canary corruption on early connect failure.

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.
Valentin V. Bartenev hai 3 meses
pai
achega
68e951e204
Modificáronse 1 ficheiros con 29 adicións e 0 borrados
  1. 29 0
      src/helpers/mqtt/MQTTUplink.cpp

+ 29 - 0
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;
   }