From 84e382387d928598e1ed9c18f5085cb8d5f47365 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 00:09:14 -1000 Subject: [PATCH 1/6] [ota] Fix ESP32-S3 OTA crash with hardware SHA acceleration on IDF 5.5.x --- esphome/components/esphome/ota/ota_esphome.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index ba25c69fae..dccaf781d8 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -558,13 +558,11 @@ bool ESPHomeOTAComponent::handle_auth_send_() { // [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce // [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash - // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame - // (no passing to other functions). All hash operations must happen in this function. - sha256::SHA256 hasher; - - const size_t hex_size = hasher.get_size() * 2; - const size_t nonce_len = hasher.get_size() / 4; - const size_t auth_buf_size = 1 + 3 * hex_size; + // Allocate auth buffer before creating SHA256 hasher to avoid potential + // heap/DMA interactions on ESP32-S3 with hardware SHA acceleration + constexpr size_t hex_size = SHA256_HEX_SIZE; + constexpr size_t nonce_len = 8; // SHA256 digest size (32) / 4 + constexpr size_t auth_buf_size = 1 + 3 * hex_size; this->auth_buf_ = std::make_unique(auth_buf_size); this->auth_buf_pos_ = 0; @@ -575,6 +573,10 @@ bool ESPHomeOTAComponent::handle_auth_send_() { return false; } + // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame + // (no passing to other functions). All hash operations must happen in this function. + // Create hasher AFTER heap allocations to avoid potential cache/DMA interference. + sha256::SHA256 hasher; hasher.init(); hasher.add(buf, nonce_len); hasher.calculate(); From 72892b89133fabb3930f2e4dca64147e25e3be32 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 00:31:40 -1000 Subject: [PATCH 2/6] fix --- esphome/components/esphome/ota/__init__.py | 2 +- esphome/components/esphome/ota/ota_esphome.cpp | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/esphome/components/esphome/ota/__init__.py b/esphome/components/esphome/ota/__init__.py index 2f637d714d..f6b6e80d97 100644 --- a/esphome/components/esphome/ota/__init__.py +++ b/esphome/components/esphome/ota/__init__.py @@ -28,7 +28,7 @@ CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] -AUTO_LOAD = ["sha256", "socket"] +AUTO_LOAD = ["md5", "sha256", "socket"] esphome = cg.esphome_ns.namespace("esphome") diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index dccaf781d8..7669cffcd9 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -1,6 +1,7 @@ #include "ota_esphome.h" #ifdef USE_OTA #ifdef USE_OTA_PASSWORD +#include "esphome/components/md5/md5.h" #include "esphome/components/sha256/sha256.h" #endif #include "esphome/components/network/util.h" @@ -575,8 +576,11 @@ bool ESPHomeOTAComponent::handle_auth_send_() { // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame // (no passing to other functions). All hash operations must happen in this function. - // Create hasher AFTER heap allocations to avoid potential cache/DMA interference. + // NOTE: On ESP32-S3 with IDF 5.5.x, having only SHA256 on the stack causes crashes with + // hardware SHA acceleration. Adding an MD5 object provides the necessary stack alignment. sha256::SHA256 hasher; + md5::MD5Digest md5_dummy; // Required for ESP32-S3 IDF 5.5.x stack alignment + (void) md5_dummy; // Suppress unused variable warning hasher.init(); hasher.add(buf, nonce_len); hasher.calculate(); @@ -636,7 +640,11 @@ bool ESPHomeOTAComponent::handle_auth_read_() { // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame // (no passing to other functions). All hash operations must happen in this function. + // NOTE: On ESP32-S3 with IDF 5.5.x, having only SHA256 on the stack causes crashes with + // hardware SHA acceleration. Adding an MD5 object provides the necessary stack alignment. sha256::SHA256 hasher; + md5::MD5Digest md5_dummy; // Required for ESP32-S3 IDF 5.5.x stack alignment + (void) md5_dummy; // Suppress unused variable warning hasher.init(); hasher.add(this->password_.c_str(), this->password_.length()); From f5ae09056c8e420ca7bc9a0de9015fda125f8ef5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 00:34:36 -1000 Subject: [PATCH 3/6] cleanup --- .../components/esphome/ota/ota_esphome.cpp | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index 7669cffcd9..be0dc61607 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -559,11 +559,17 @@ bool ESPHomeOTAComponent::handle_auth_send_() { // [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce // [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash - // Allocate auth buffer before creating SHA256 hasher to avoid potential - // heap/DMA interactions on ESP32-S3 with hardware SHA acceleration - constexpr size_t hex_size = SHA256_HEX_SIZE; - constexpr size_t nonce_len = 8; // SHA256 digest size (32) / 4 - constexpr size_t auth_buf_size = 1 + 3 * hex_size; + // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame + // (no passing to other functions). All hash operations must happen in this function. + // NOTE: On ESP32-S3 with IDF 5.5.x, having only SHA256 on the stack causes crashes with + // hardware SHA acceleration. Adding an MD5 object provides the necessary stack alignment. + sha256::SHA256 hasher; + md5::MD5Digest md5_dummy; // Required for ESP32-S3 IDF 5.5.x stack alignment + (void) md5_dummy; // Suppress unused variable warning + + const size_t hex_size = hasher.get_size() * 2; + const size_t nonce_len = hasher.get_size() / 4; + const size_t auth_buf_size = 1 + 3 * hex_size; this->auth_buf_ = std::make_unique(auth_buf_size); this->auth_buf_pos_ = 0; @@ -574,13 +580,6 @@ bool ESPHomeOTAComponent::handle_auth_send_() { return false; } - // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame - // (no passing to other functions). All hash operations must happen in this function. - // NOTE: On ESP32-S3 with IDF 5.5.x, having only SHA256 on the stack causes crashes with - // hardware SHA acceleration. Adding an MD5 object provides the necessary stack alignment. - sha256::SHA256 hasher; - md5::MD5Digest md5_dummy; // Required for ESP32-S3 IDF 5.5.x stack alignment - (void) md5_dummy; // Suppress unused variable warning hasher.init(); hasher.add(buf, nonce_len); hasher.calculate(); From 3e6d77743947e960a835207d43dac8fe7787744e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 00:46:15 -1000 Subject: [PATCH 4/6] fix --- esphome/components/esphome/ota/__init__.py | 2 +- esphome/components/esphome/ota/ota_esphome.cpp | 17 ++++++----------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/esphome/components/esphome/ota/__init__.py b/esphome/components/esphome/ota/__init__.py index f6b6e80d97..2f637d714d 100644 --- a/esphome/components/esphome/ota/__init__.py +++ b/esphome/components/esphome/ota/__init__.py @@ -28,7 +28,7 @@ CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] -AUTO_LOAD = ["md5", "sha256", "socket"] +AUTO_LOAD = ["sha256", "socket"] esphome = cg.esphome_ns.namespace("esphome") diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index be0dc61607..f71163f79e 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -1,7 +1,6 @@ #include "ota_esphome.h" #ifdef USE_OTA #ifdef USE_OTA_PASSWORD -#include "esphome/components/md5/md5.h" #include "esphome/components/sha256/sha256.h" #endif #include "esphome/components/network/util.h" @@ -561,11 +560,9 @@ bool ESPHomeOTAComponent::handle_auth_send_() { // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame // (no passing to other functions). All hash operations must happen in this function. - // NOTE: On ESP32-S3 with IDF 5.5.x, having only SHA256 on the stack causes crashes with - // hardware SHA acceleration. Adding an MD5 object provides the necessary stack alignment. - sha256::SHA256 hasher; - md5::MD5Digest md5_dummy; // Required for ESP32-S3 IDF 5.5.x stack alignment - (void) md5_dummy; // Suppress unused variable warning + // NOTE: On ESP32-S3 with IDF 5.5.x, the SHA256 context must be properly aligned for + // hardware SHA acceleration DMA operations. + alignas(32) sha256::SHA256 hasher; const size_t hex_size = hasher.get_size() * 2; const size_t nonce_len = hasher.get_size() / 4; @@ -639,11 +636,9 @@ bool ESPHomeOTAComponent::handle_auth_read_() { // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame // (no passing to other functions). All hash operations must happen in this function. - // NOTE: On ESP32-S3 with IDF 5.5.x, having only SHA256 on the stack causes crashes with - // hardware SHA acceleration. Adding an MD5 object provides the necessary stack alignment. - sha256::SHA256 hasher; - md5::MD5Digest md5_dummy; // Required for ESP32-S3 IDF 5.5.x stack alignment - (void) md5_dummy; // Suppress unused variable warning + // NOTE: On ESP32-S3 with IDF 5.5.x, the SHA256 context must be properly aligned for + // hardware SHA acceleration DMA operations. + alignas(32) sha256::SHA256 hasher; hasher.init(); hasher.add(this->password_.c_str(), this->password_.length()); From b40de61224ce3fd6440bd118ce1419685607a42e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 00:48:40 -1000 Subject: [PATCH 5/6] cleanup --- esphome/components/sha256/sha256.h | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/esphome/components/sha256/sha256.h b/esphome/components/sha256/sha256.h index a2b62799e1..17d80636f1 100644 --- a/esphome/components/sha256/sha256.h +++ b/esphome/components/sha256/sha256.h @@ -22,6 +22,18 @@ namespace esphome::sha256 { +/// SHA256 hash implementation. +/// +/// CRITICAL for ESP32-S3 with IDF 5.5.x hardware SHA acceleration: +/// 1. SHA256 objects MUST be declared with `alignas(32)` for proper DMA alignment +/// 2. The object MUST stay in the same stack frame (no passing to other functions) +/// 3. NO Variable Length Arrays (VLAs) in the same function +/// +/// Example usage: +/// alignas(32) sha256::SHA256 hasher; +/// hasher.init(); +/// hasher.add(data, len); +/// hasher.calculate(); class SHA256 : public esphome::HashBase { public: SHA256() = default; @@ -39,10 +51,8 @@ class SHA256 : public esphome::HashBase { protected: #if defined(USE_ESP32) || defined(USE_LIBRETINY) - // CRITICAL: The mbedtls context MUST be stack-allocated (not a pointer) for ESP32-S3 hardware SHA acceleration. - // The ESP32-S3 DMA engine references this structure's memory addresses. If the context is passed to another - // function (crossing stack frames) or if VLAs are present, the DMA operations will corrupt memory and produce - // truncated/incorrect hash results. + // The mbedtls context for ESP32-S3 hardware SHA requires proper alignment and stack frame constraints. + // See class documentation above for critical requirements. mbedtls_sha256_context ctx_{}; #elif defined(USE_ESP8266) || defined(USE_RP2040) br_sha256_context ctx_{}; From ffb15b592c5fba23ec47d55c5a6f74b1d29b0e8f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Jan 2026 00:50:43 -1000 Subject: [PATCH 6/6] cleanup --- esphome/components/sha256/sha256.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/esphome/components/sha256/sha256.cpp b/esphome/components/sha256/sha256.cpp index 32abbd739d..48559d7c73 100644 --- a/esphome/components/sha256/sha256.cpp +++ b/esphome/components/sha256/sha256.cpp @@ -10,23 +10,26 @@ namespace esphome::sha256 { #if defined(USE_ESP32) || defined(USE_LIBRETINY) -// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION REQUIREMENTS: +// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION REQUIREMENTS (IDF 5.5.x): // // The ESP32-S3 uses hardware DMA for SHA acceleration. The mbedtls_sha256_context structure contains -// internal state that the DMA engine references. This imposes two critical constraints: +// internal state that the DMA engine references. This imposes three critical constraints: // -// 1. NO VARIABLE LENGTH ARRAYS (VLAs): VLAs corrupt the stack layout, causing the DMA engine to +// 1. ALIGNMENT: The SHA256 object MUST be declared with `alignas(32)` for proper DMA alignment. +// Without this, the DMA engine may crash with an abort in sha_hal_read_digest(). +// +// 2. NO VARIABLE LENGTH ARRAYS (VLAs): VLAs corrupt the stack layout, causing the DMA engine to // write to incorrect memory locations. This results in null pointer dereferences and crashes. // ALWAYS use fixed-size arrays (e.g., char buf[65], not char buf[size+1]). // -// 2. SAME STACK FRAME ONLY: The SHA256 object must be created and used entirely within the same +// 3. SAME STACK FRAME ONLY: The SHA256 object must be created and used entirely within the same // function. NEVER pass the SHA256 object or HashBase pointer to another function. When the stack // frame changes (function call/return), the DMA references become invalid and will produce // truncated hash output (20 bytes instead of 32) or corrupt memory. // // CORRECT USAGE: // void my_function() { -// sha256::SHA256 hasher; // Created locally +// alignas(32) sha256::SHA256 hasher; // Created locally with proper alignment // hasher.init(); // hasher.add(data, len); // Any size, no chunking needed // hasher.calculate(); @@ -36,7 +39,7 @@ namespace esphome::sha256 { // // INCORRECT USAGE (WILL FAIL ON ESP32-S3): // void my_function() { -// sha256::SHA256 hasher; +// sha256::SHA256 hasher; // WRONG: Missing alignas(32) // helper(&hasher); // WRONG: Passed to different stack frame // } // void helper(HashBase *h) {