From e44c357a020feefae1a5eefd041573fdf109dd70 Mon Sep 17 00:00:00 2001 From: Mahavir Jain Date: Wed, 2 Sep 2026 13:54:58 +0530 Subject: [PATCH] fix(mbedtls): read crt bundle byte-wise to avoid misaligned flash access The offset table and the per-cert length fields of the certificate bundle were read through uint16_t*/uint32_t* casts, which compile to halfword/word loads at addresses with no alignment guarantee: bundles supplied via esp_crt_bundle_set() can start anywhere, and cert entries are byte-packed, so their 16-bit fields land at arbitrary offsets. On chips with SOC_CPU_MISALIGNED_ACCESS_ON_PMP_MISMATCH_ISSUE (DIG-694: ESP32-C6/H2/H21) a misaligned load from memory-mapped flash can take a spurious "Load access fault" when it sits within two instructions of an access to a differently-permissioned region, observed as a crash in esp_crt_check_bundle()/CA callback during TLS handshakes with a bundle that happened to be placed at an odd address. --- .../mbedtls/esp_crt_bundle/esp_crt_bundle.c | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/components/mbedtls/esp_crt_bundle/esp_crt_bundle.c b/components/mbedtls/esp_crt_bundle/esp_crt_bundle.c index ca6608c781c..38394021b24 100644 --- a/components/mbedtls/esp_crt_bundle/esp_crt_bundle.c +++ b/components/mbedtls/esp_crt_bundle/esp_crt_bundle.c @@ -71,14 +71,19 @@ typedef const uint8_t* cert_t; static bundle_t s_crt_bundle; -// Read a 16-bit value stored in little-endian format from the given address +/* Read little-endian values byte-wise: bundle fields are byte-packed with no + * alignment guarantee, and a misaligned load from flash can spuriously fault + * on chips with SOC_CPU_MISALIGNED_ACCESS_ON_PMP_MISMATCH_ISSUE (DIG-694). + */ static uint16_t get16_le(const uint8_t* ptr) { -#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) - return *((const uint16_t*)ptr); -#else - return (((uint16_t)ptr[1]) << 8) | ptr[0]; -#endif + return (uint16_t)ptr[0] | ((uint16_t)ptr[1] << 8); +} + +static uint32_t get32_le(const uint8_t* ptr) +{ + return (uint32_t)ptr[0] | ((uint32_t)ptr[1] << 8) + | ((uint32_t)ptr[2] << 16) | ((uint32_t)ptr[3] << 24); } static uint16_t esp_crt_get_name_len(const cert_t cert) @@ -110,7 +115,7 @@ static uint32_t esp_crt_get_len(const cert_t cert) static uint32_t esp_crt_get_cert_offset(const bundle_t bundle, const uint32_t index) { - return ((const uint32_t*)bundle)[index]; + return get32_le(bundle + index * sizeof(uint32_t)); } static uint32_t esp_crt_get_certcount(const bundle_t bundle) @@ -409,16 +414,15 @@ static bool esp_crt_check_bundle(const uint8_t* const x509_bundle, const size_t return false; } - // Pointer to the first offset entry - const uint32_t* offsets = (const uint32_t*)x509_bundle; - - if (unlikely(offsets[0] == 0 || (offsets[0] % sizeof(uint32_t)) != 0)) { + // The bundle base may be unaligned; read offsets byte-wise via esp_crt_get_cert_offset() + if (unlikely(esp_crt_get_cert_offset(x509_bundle, 0) == 0 + || (esp_crt_get_cert_offset(x509_bundle, 0) % sizeof(uint32_t)) != 0)) { // First offset is invalid. // The first certificate must start after N uint32_t offset values. return false; } - if (unlikely(offsets[0] >= bundle_size)) { + if (unlikely(esp_crt_get_cert_offset(x509_bundle, 0) >= bundle_size)) { // First cert starts beyond end of bundle return false; } @@ -437,7 +441,7 @@ static bool esp_crt_check_bundle(const uint8_t* const x509_bundle, const size_t // Check all offsets for consistency with certificate data for (uint32_t i = 0; i < num_certs - 1; ++i) { - const uint32_t off = offsets[i]; + const uint32_t off = esp_crt_get_cert_offset(x509_bundle, i); if (unlikely((uint64_t)off + CRT_HEADER_SIZE > bundle_size)) { return false; } @@ -445,14 +449,15 @@ static bool esp_crt_check_bundle(const uint8_t* const x509_bundle, const size_t // The next offset in the list must point to right after the current cert const uint32_t expected_next_offset = off + esp_crt_get_len(cert); - if (unlikely(offsets[i + 1] != expected_next_offset || expected_next_offset >= bundle_size)) { + if (unlikely(esp_crt_get_cert_offset(x509_bundle, i + 1) != expected_next_offset + || expected_next_offset >= bundle_size)) { return false; } } // The loop above stops at num_certs - 1, so the final certificate's extent is never // validated; check it explicitly so its key data cannot run past the bundle (CWE-125). - const uint32_t last_off = offsets[num_certs - 1]; + const uint32_t last_off = esp_crt_get_cert_offset(x509_bundle, num_certs - 1); if (unlikely((uint64_t)last_off + CRT_HEADER_SIZE > bundle_size)) { return false; }