Files
HaloKeymind/src/helpers/ESP32WsTransportFix.cpp
T
mikecarper c1caa5ad81 Merge observer firmware and webconfig updates
# Conflicts:
#	MQTT_INTERNALS.md
#	build.sh
#	docs/cli_commands.md
#	examples/simple_repeater/MyMesh.cpp
#	examples/simple_repeater/MyMesh.h
#	examples/simple_repeater/UITask.cpp
#	examples/simple_room_server/MyMesh.cpp
#	examples/simple_room_server/MyMesh.h
#	examples/simple_room_server/UITask.cpp
#	examples/simple_sensor/SensorMesh.cpp
#	platformio.ini
#	scripts/generate_webconfig_html.py
#	src/MeshCore.h
#	src/helpers/CommonCLI.cpp
#	src/helpers/CommonCLI.h
#	src/helpers/CommonCLI_Observer.cpp
#	src/helpers/ESP32Board.h
#	src/helpers/MQTTMessageBuilder.cpp
#	src/helpers/MQTTPresets.h
#	src/helpers/NRF52Board.cpp
#	src/helpers/NRF52Board.h
#	src/helpers/bridges/MQTTBridge.cpp
#	src/helpers/bridges/MQTTBridge.h
#	src/helpers/esp32/WebConfigServer.cpp
#	src/helpers/esp32/WebConfigServer.h
#	src/helpers/radiolib/RadioLibWrappers.cpp
#	src/helpers/radiolib/RadioLibWrappers.h
#	variants/lilygo_tbeam_SX1262/platformio.ini
#	variants/lilygo_tbeam_SX1276/platformio.ini
#	variants/lilygo_tlora_v2_1/platformio.ini
#	webui/index.html
2026-07-24 02:16:47 -07:00

131 lines
5.0 KiB
C++

#ifdef ESP_PLATFORM
// Link-time workaround for an off-by-one heap overflow in ESP-IDF v4.4's
// WebSocket transport (components/tcp_transport/transport_ws.c), which ships
// PRECOMPILED in the Arduino-ESP32 2.x SDK (libtcp_transport.a) and cannot be
// patched at source level.
//
// The bug (transport_ws.c, ws_connect() response-read loop):
//
// header_len += len;
// ws->buffer[header_len] = '\0'; // header_len can reach WS_BUFFER_SIZE
// } while (... && header_len < WS_BUFFER_SIZE);
//
// ws->buffer is malloc(WS_BUFFER_SIZE) (1024). When a wss:// endpoint answers
// the WebSocket upgrade with >= 1024 bytes of HTTP response before the blank
// line terminator (typical for a down/misconfigured broker behind a proxy or
// CDN that serves a large HTML error page), the final iteration writes one
// '\0' one byte past the block. With heap poisoning enabled that zeroes the
// LSB of the tail canary (0xbaad5678 -> 0xbaad5600); the corruption then sits
// silent until the block is freed -- which happens in ws_destroy() during
// esp_mqtt_client_destroy(), i.e. MQTTBridge::end() -- and the free asserts:
//
// CORRUPT HEAP: Bad tail at 0x.... Expected 0xbaad5678 got 0xbaad5600
// assert failed: multi_heap_free multi_heap_poisoning.c:259
//
// On observer builds that teardown runs at the start of the deferred
// `ota update`, so a single down wss broker made every online OTA panic and
// reboot before the download began (backtrace decoded from a Heltec V3 on
// v1.16.0.11: free <- ws_destroy <- esp_transport_list_destroy <-
// esp_mqtt_client_destroy <- ~PsychicMqttClient <- destroySlotClients <-
// MQTTBridge::end <- MyMesh::setBridgeState <- MyMesh::loop).
//
// Fix: [esp32_base] adds `-Wl,--wrap=esp_transport_ws_init`, so every
// creation of a WS transport (esp-mqtt does one per wss slot) is routed
// through __wrap_esp_transport_ws_init below, which replaces the freshly
// allocated 1024-byte buffer with a (WS_BUFFER_SIZE + 1)-byte one. The
// out-of-bounds index WS_BUFFER_SIZE then lands on our extra byte and the
// handshake fails cleanly ("Upgrade" header not found) instead of corrupting
// the heap. Upstream fixed this in ESP-IDF 5.x, so this file compiles to a
// pass-through there and can be deleted (together with the --wrap flag) when
// the fork moves to Arduino core 3.x.
//
// transport_ws_t below is copied verbatim from ESP-IDF release/v4.4
// transport_ws.c (the struct is file-private, so it is not in any shipped
// header). Source fidelity was verified against the shipped binary: addr2line
// on the crash backtrace resolves to the exact line numbers of that file
// (e.g. free(ws->buffer) at transport_ws.c:546). Only the first two members
// (path, buffer) are dereferenced here.
#include "esp_idf_version.h"
#if ESP_IDF_VERSION_MAJOR == 4
#include <stdlib.h>
#include "sdkconfig.h"
#include "esp_transport.h"
#include "esp_transport_ws.h"
#ifndef CONFIG_WS_BUFFER_SIZE
#define CONFIG_WS_BUFFER_SIZE 1024
#endif
// --- copied from ESP-IDF release/v4.4 components/tcp_transport/transport_ws.c ---
typedef struct {
uint8_t opcode;
char mask_key[4];
int payload_len;
int bytes_remaining;
bool header_received;
} ws_transport_frame_state_t;
typedef struct {
char *path;
char *buffer;
char *sub_protocol;
char *user_agent;
char *headers;
bool propagate_control_frames;
ws_transport_frame_state_t frame_state;
esp_transport_handle_t parent;
} transport_ws_t;
// --------------------------------------------------------------------------------
extern "C" {
esp_transport_handle_t __real_esp_transport_ws_init(esp_transport_handle_t parent_handle);
esp_transport_handle_t __wrap_esp_transport_ws_init(esp_transport_handle_t parent_handle) {
esp_transport_handle_t t = __real_esp_transport_ws_init(parent_handle);
if (t != nullptr) {
transport_ws_t* ws = (transport_ws_t*)esp_transport_get_context_data(t);
if (ws != nullptr && ws->buffer != nullptr) {
// The buffer is untouched at this point (allocated moments ago inside
// __real_esp_transport_ws_init), so a swap is safe.
char* padded = (char*)malloc(CONFIG_WS_BUFFER_SIZE + 1);
if (padded != nullptr) {
free(ws->buffer);
ws->buffer = padded;
}
// On alloc failure keep the original buffer: same behavior as before
// this fix, which is still strictly better than failing init here.
}
}
return t;
}
} // extern "C"
#else // ESP_IDF_VERSION_MAJOR != 4
// IDF 5.x fixed the overflow upstream; keep a pass-through so the --wrap flag
// (set for all ESP32 envs in [esp32_base]) still links if anything references
// the symbol.
#include "esp_transport.h"
#include "esp_transport_ws.h"
extern "C" {
esp_transport_handle_t __real_esp_transport_ws_init(esp_transport_handle_t parent_handle);
esp_transport_handle_t __wrap_esp_transport_ws_init(esp_transport_handle_t parent_handle) {
return __real_esp_transport_ws_init(parent_handle);
}
} // extern "C"
#endif // ESP_IDF_VERSION_MAJOR
#endif // ESP_PLATFORM