From 6625e52842c0eb8319eaff193f9d618f2c42c0b1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 14 Jan 2026 19:11:10 -1000 Subject: [PATCH] [core] Fix ESP32-S2/S3 hardware SHA crash by aligning HashBase digest buffer --- .../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 | 4 +++- 5 files changed, 24 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 9f8ae3277e7..d69a438578a 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 b2ae1856875..df2ea98f2c8 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 48559d7c73d..933e3ff8031 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-S2/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 three critical constraints: +// The ESP32-S2/S3 uses hardware DMA for SHA acceleration. The DMA engine requires proper +// alignment of the digest output buffer. This is handled automatically via HashBase::digest_ +// which has alignas(32). This imposes two critical constraints: // -// 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 ON ESP32-S2/S3): // 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 17d80636f13..5fab9bde613 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-S2/S3 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 0c1c2dce330..5b62fd0d17e 100644 --- a/esphome/core/hash_base.h +++ b/esphome/core/hash_base.h @@ -44,7 +44,9 @@ class HashBase { virtual size_t get_size() const = 0; protected: - uint8_t digest_[32]; // Storage sized for max(MD5=16, SHA256=32) bytes + // 32-byte alignment required for ESP32-S2/S3 hardware SHA DMA operations. + // This also sets the class alignment to 32, ensuring derived objects are properly aligned. + alignas(32) uint8_t digest_[32]; // Storage sized for max(MD5=16, SHA256=32) bytes }; } // namespace esphome