From 9003844eda7d7ddeea60cab318e914beea59f471 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 15 Jan 2026 08:29:11 -1000 Subject: [PATCH] [core] Fix ESP32-S2/S3 hardware SHA crash by aligning HashBase digest buffer (#13234) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- .../update/esp32_hosted_update.cpp | 6 ++---- .../components/esphome/ota/ota_esphome.cpp | 12 ++++------- esphome/components/sha256/sha256.cpp | 20 +++++++++---------- esphome/components/sha256/sha256.h | 11 +++++----- esphome/core/hash_base.h | 10 +++++++++- 5 files changed, 30 insertions(+), 29 deletions(-) diff --git a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp index 9f8ae3277e..d69a438578 100644 --- a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp +++ b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp @@ -294,8 +294,7 @@ bool Esp32HostedUpdate::stream_firmware_to_coprocessor_() { } // Stream firmware to coprocessor while computing SHA256 - // Hardware SHA acceleration requires 32-byte alignment on some chips (ESP32-S3 with IDF 5.5.x+) - alignas(32) sha256::SHA256 hasher; + sha256::SHA256 hasher; hasher.init(); uint8_t buffer[CHUNK_SIZE]; @@ -352,8 +351,7 @@ bool Esp32HostedUpdate::write_embedded_firmware_to_coprocessor_() { } // Verify SHA256 before writing - // Hardware SHA acceleration requires 32-byte alignment on some chips (ESP32-S3 with IDF 5.5.x+) - alignas(32) sha256::SHA256 hasher; + sha256::SHA256 hasher; hasher.init(); hasher.add(this->firmware_data_, this->firmware_size_); hasher.calculate(); diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index b2ae185687..df2ea98f2c 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -563,11 +563,9 @@ 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 + // CRITICAL ESP32-S2/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, the SHA256 context must be properly aligned for - // hardware SHA acceleration DMA operations. - alignas(32) sha256::SHA256 hasher; + sha256::SHA256 hasher; const size_t hex_size = hasher.get_size() * 2; const size_t nonce_len = hasher.get_size() / 4; @@ -639,11 +637,9 @@ bool ESPHomeOTAComponent::handle_auth_read_() { const char *cnonce = nonce + hex_size; const char *response = cnonce + hex_size; - // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame + // CRITICAL ESP32-S2/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, the SHA256 context must be properly aligned for - // hardware SHA acceleration DMA operations. - alignas(32) sha256::SHA256 hasher; + sha256::SHA256 hasher; hasher.init(); hasher.add(this->password_.c_str(), this->password_.length()); diff --git a/esphome/components/sha256/sha256.cpp b/esphome/components/sha256/sha256.cpp index 48559d7c73..23995e6534 100644 --- a/esphome/components/sha256/sha256.cpp +++ b/esphome/components/sha256/sha256.cpp @@ -10,26 +10,24 @@ namespace esphome::sha256 { #if defined(USE_ESP32) || defined(USE_LIBRETINY) -// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION REQUIREMENTS (IDF 5.5.x): +// CRITICAL ESP32 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 three critical constraints: +// ESP32 variants (except original ESP32) use DMA-based hardware SHA acceleration that requires +// 32-byte aligned digest buffers. This is handled automatically via HashBase::digest_ which has +// alignas(32) on these platforms. Two additional constraints apply: // -// 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 +// 1. 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]). // -// 3. SAME STACK FRAME ONLY: The SHA256 object must be created and used entirely within the same +// 2. 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() { -// alignas(32) sha256::SHA256 hasher; // Created locally with proper alignment +// sha256::SHA256 hasher; // hasher.init(); // hasher.add(data, len); // Any size, no chunking needed // hasher.calculate(); @@ -37,9 +35,9 @@ namespace esphome::sha256 { // // hasher destroyed when function returns // } // -// INCORRECT USAGE (WILL FAIL ON ESP32-S3): +// INCORRECT USAGE (WILL FAIL): // void my_function() { -// sha256::SHA256 hasher; // WRONG: Missing alignas(32) +// sha256::SHA256 hasher; // helper(&hasher); // WRONG: Passed to different stack frame // } // void helper(HashBase *h) { diff --git a/esphome/components/sha256/sha256.h b/esphome/components/sha256/sha256.h index 17d80636f1..bafb359485 100644 --- a/esphome/components/sha256/sha256.h +++ b/esphome/components/sha256/sha256.h @@ -24,13 +24,14 @@ 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 +/// CRITICAL for ESP32 variants (except original) with IDF 5.5.x hardware SHA acceleration: +/// 1. The object MUST stay in the same stack frame (no passing to other functions) +/// 2. NO Variable Length Arrays (VLAs) in the same function +/// +/// Note: Alignment is handled automatically via the HashBase::digest_ member. /// /// Example usage: -/// alignas(32) sha256::SHA256 hasher; +/// sha256::SHA256 hasher; /// hasher.init(); /// hasher.add(data, len); /// hasher.calculate(); diff --git a/esphome/core/hash_base.h b/esphome/core/hash_base.h index 0c1c2dce33..606cd3080c 100644 --- a/esphome/core/hash_base.h +++ b/esphome/core/hash_base.h @@ -44,7 +44,15 @@ class HashBase { virtual size_t get_size() const = 0; protected: - uint8_t digest_[32]; // Storage sized for max(MD5=16, SHA256=32) bytes +// ESP32 variants with DMA-based hardware SHA (all except original ESP32) require 32-byte aligned buffers. +// Original ESP32 uses a different hardware SHA implementation without DMA alignment requirements. +// Other platforms (ESP8266, RP2040, LibreTiny) use software SHA and don't need alignment. +// Storage sized for max(MD5=16, SHA256=32) bytes +#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32) + alignas(32) uint8_t digest_[32]; +#else + uint8_t digest_[32]; +#endif }; } // namespace esphome