Merge branch 'fix/crt-bundle-unaligned-reads' into 'master'

fix(mbedtls): read crt bundle byte-wise to avoid misaligned flash access

Closes IDF-16030

See merge request espressif/esp-idf!51600
This commit is contained in:
Mahavir Jain
2026-08-12 14:13:45 +05:30
3 changed files with 24 additions and 16 deletions
+2 -1
View File
@@ -128,7 +128,8 @@ if(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE)
add_dependencies(${COMPONENT_LIB} custom_bundle)
target_add_binary_data(${COMPONENT_LIB} ${crt_bundle} BINARY)
# ALIGN 4: the offset table at the bundle head is 32-bit values
target_add_binary_data(${COMPONENT_LIB} ${crt_bundle} BINARY ALIGN 4)
set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
APPEND PROPERTY ADDITIONAL_CLEAN_FILES
"${crt_bundle}")
@@ -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,19 +441,20 @@ 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);
cert_t cert = x509_bundle + off;
// 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(last_off >= bundle_size)) {
return false;
}
@@ -83,6 +83,8 @@ append("${data}")
make_and_append_identifier("_binary_${varname}_end" "for objcopy compatibility")
append_line("")
# Keep the length aligned on the word boundary (read as a 32-bit `<name>_length`)
append_line(".balign 4")
if(FILE_TYPE STREQUAL "TEXT")
make_and_append_identifier("${varname}_length" "not including null byte")
else()