diff --git a/components/bootloader/subproject/components/micro-ecc/CMakeLists.txt b/components/bootloader/subproject/components/micro-ecc/CMakeLists.txt index 7d4bfc4d1..4e630af61 100644 --- a/components/bootloader/subproject/components/micro-ecc/CMakeLists.txt +++ b/components/bootloader/subproject/components/micro-ecc/CMakeLists.txt @@ -1,3 +1,3 @@ -# only compile the "micro-ecc/uECC.c" source file -idf_component_register(SRCS "micro-ecc/uECC.c" - INCLUDE_DIRS micro-ecc) +# only compile the "uECC_verify_antifault.c" file which includes the "micro-ecc/uECC.c" source file +idf_component_register(SRCS "uECC_verify_antifault.c" + INCLUDE_DIRS . micro-ecc) diff --git a/components/bootloader/subproject/components/micro-ecc/component.mk b/components/bootloader/subproject/components/micro-ecc/component.mk index 8c569df59..0ce055be0 100644 --- a/components/bootloader/subproject/components/micro-ecc/component.mk +++ b/components/bootloader/subproject/components/micro-ecc/component.mk @@ -1,8 +1,6 @@ -# only compile the micro-ecc/uECC.c source file -# (SRCDIRS is needed so build system can find the source file) -COMPONENT_SRCDIRS := micro-ecc -COMPONENT_OBJS := micro-ecc/uECC.o +# only compile the "uECC_verify_antifault.c" file which includes the "micro-ecc/uECC.c" source file +COMPONENT_SRCDIRS := . -COMPONENT_ADD_INCLUDEDIRS := micro-ecc +COMPONENT_ADD_INCLUDEDIRS := . micro-ecc COMPONENT_SUBMODULES := micro-ecc diff --git a/components/bootloader/subproject/components/micro-ecc/uECC_verify_antifault.c b/components/bootloader/subproject/components/micro-ecc/uECC_verify_antifault.c new file mode 100644 index 000000000..87ccf2c0e --- /dev/null +++ b/components/bootloader/subproject/components/micro-ecc/uECC_verify_antifault.c @@ -0,0 +1,141 @@ +/* Copyright 2014, Kenneth MacKay. Licensed under the BSD 2-clause license. + + Modifications Copyright 2020, Espressif Systems (Shanghai) PTE LTD. Licensed under the BSD + 2-clause license. +*/ + +/* uECC_verify() calls a number of static functions form here and + uses other definitions, so we just build that whole source file here and then append + our modified version uECC_verify_antifault(). */ +#include "micro-ecc/uECC.c" + +/* Version of uECC_verify() which also copies message_hash into verified_hash, + but only if the signature is valid. Does this in an FI resistant way. +*/ +int uECC_verify_antifault(const uint8_t *public_key, + const uint8_t *message_hash, + unsigned hash_size, + const uint8_t *signature, + uECC_Curve curve, + uint8_t *verified_hash) { + uECC_word_t u1[uECC_MAX_WORDS], u2[uECC_MAX_WORDS]; + uECC_word_t z[uECC_MAX_WORDS]; + uECC_word_t sum[uECC_MAX_WORDS * 2]; + uECC_word_t rx[uECC_MAX_WORDS]; + uECC_word_t ry[uECC_MAX_WORDS]; + uECC_word_t tx[uECC_MAX_WORDS]; + uECC_word_t ty[uECC_MAX_WORDS]; + uECC_word_t tz[uECC_MAX_WORDS]; + const uECC_word_t *points[4]; + const uECC_word_t *point; + bitcount_t num_bits; + bitcount_t i; +#if uECC_VLI_NATIVE_LITTLE_ENDIAN + uECC_word_t *_public = (uECC_word_t *)public_key; +#else + uECC_word_t _public[uECC_MAX_WORDS * 2]; +#endif + uECC_word_t r[uECC_MAX_WORDS], s[uECC_MAX_WORDS]; + wordcount_t num_words = curve->num_words; + wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits); + + rx[num_n_words - 1] = 0; + r[num_n_words - 1] = 0; + s[num_n_words - 1] = 0; + +#if uECC_VLI_NATIVE_LITTLE_ENDIAN + bcopy((uint8_t *) r, signature, curve->num_bytes); + bcopy((uint8_t *) s, signature + curve->num_bytes, curve->num_bytes); +#else + uECC_vli_bytesToNative(_public, public_key, curve->num_bytes); + uECC_vli_bytesToNative( + _public + num_words, public_key + curve->num_bytes, curve->num_bytes); + uECC_vli_bytesToNative(r, signature, curve->num_bytes); + uECC_vli_bytesToNative(s, signature + curve->num_bytes, curve->num_bytes); +#endif + + /* r, s must not be 0. */ + if (uECC_vli_isZero(r, num_words) || uECC_vli_isZero(s, num_words)) { + return 0; + } + + /* r, s must be < n. */ + if (uECC_vli_cmp(curve->n, r, num_n_words) != 1 || + uECC_vli_cmp(curve->n, s, num_n_words) != 1) { + return 0; + } + + /* Calculate u1 and u2. */ + uECC_vli_modInv(z, s, curve->n, num_n_words); /* z = 1/s */ + u1[num_n_words - 1] = 0; + bits2int(u1, message_hash, hash_size, curve); + uECC_vli_modMult(u1, u1, z, curve->n, num_n_words); /* u1 = e/s */ + uECC_vli_modMult(u2, r, z, curve->n, num_n_words); /* u2 = r/s */ + + /* Calculate sum = G + Q. */ + uECC_vli_set(sum, _public, num_words); + uECC_vli_set(sum + num_words, _public + num_words, num_words); + uECC_vli_set(tx, curve->G, num_words); + uECC_vli_set(ty, curve->G + num_words, num_words); + uECC_vli_modSub(z, sum, tx, curve->p, num_words); /* z = x2 - x1 */ + XYcZ_add(tx, ty, sum, sum + num_words, curve); + uECC_vli_modInv(z, z, curve->p, num_words); /* z = 1/z */ + apply_z(sum, sum + num_words, z, curve); + + /* Use Shamir's trick to calculate u1*G + u2*Q */ + points[0] = 0; + points[1] = curve->G; + points[2] = _public; + points[3] = sum; + num_bits = smax(uECC_vli_numBits(u1, num_n_words), + uECC_vli_numBits(u2, num_n_words)); + + point = points[(!!uECC_vli_testBit(u1, num_bits - 1)) | + ((!!uECC_vli_testBit(u2, num_bits - 1)) << 1)]; + uECC_vli_set(rx, point, num_words); + uECC_vli_set(ry, point + num_words, num_words); + uECC_vli_clear(z, num_words); + z[0] = 1; + + for (i = num_bits - 2; i >= 0; --i) { + uECC_word_t index; + curve->double_jacobian(rx, ry, z, curve); + + index = (!!uECC_vli_testBit(u1, i)) | ((!!uECC_vli_testBit(u2, i)) << 1); + point = points[index]; + if (point) { + uECC_vli_set(tx, point, num_words); + uECC_vli_set(ty, point + num_words, num_words); + apply_z(tx, ty, z, curve); + uECC_vli_modSub(tz, rx, tx, curve->p, num_words); /* Z = x2 - x1 */ + XYcZ_add(tx, ty, rx, ry, curve); + uECC_vli_modMult_fast(z, z, tz, curve); + } + } + + uECC_vli_modInv(z, z, curve->p, num_words); /* Z = 1/Z */ + apply_z(rx, ry, z, curve); + + /* v = x1 (mod n) */ + if (uECC_vli_cmp(curve->n, rx, num_n_words) != 1) { + uECC_vli_sub(rx, rx, curve->n, num_n_words); + } + + /* Anti-FI addition. Copy message_hash into verified_hash, but do it in a + way that it will only happen if v == r (ie, rx == r) + */ + const uECC_word_t *mhash_words = (const uECC_word_t *)message_hash; + uECC_word_t *vhash_words = (uECC_word_t *)verified_hash; + unsigned hash_words = hash_size / sizeof(uECC_word_t); + for (int w = 0; w < hash_words; w++) { + /* note: using curve->num_words here to encourage compiler to re-read this variable */ + vhash_words[w] = mhash_words[w] ^ rx[w % curve->num_words] ^ r[w % curve->num_words]; + } + /* Curve may be longer than hash, in which case keep reading the rest of the bytes */ + for (int w = hash_words; w < curve->num_words; w++) { + vhash_words[w % hash_words] |= rx[w] | r[w]; + } + + /* Accept only if v == r. */ + return (int)(uECC_vli_equal(rx, r, num_words)); +} diff --git a/components/bootloader/subproject/components/micro-ecc/uECC_verify_antifault.h b/components/bootloader/subproject/components/micro-ecc/uECC_verify_antifault.h new file mode 100644 index 000000000..fa14b24f6 --- /dev/null +++ b/components/bootloader/subproject/components/micro-ecc/uECC_verify_antifault.h @@ -0,0 +1,18 @@ +/* Copyright 2014, Kenneth MacKay. Licensed under the BSD 2-clause license. + + Modifications Copyright 2020, Espressif Systems (Shanghai) PTE LTD. Licensed under the BSD + 2-clause license. +*/ +#pragma once +#include "uECC.h" + +/* Version uECC_verify() that also copies message_hash to verified_hash + if the signature is valid, and does it in a way that is harder to attack + with fault injection. +*/ +int uECC_verify_antifault(const uint8_t *public_key, + const uint8_t *message_hash, + unsigned hash_size, + const uint8_t *signature, + uECC_Curve curve, + uint8_t *verified_hash); diff --git a/components/bootloader/subproject/main/ld/esp32/bootloader.ld b/components/bootloader/subproject/main/ld/esp32/bootloader.ld index 504e9b423..b6c5f8987 100644 --- a/components/bootloader/subproject/main/ld/esp32/bootloader.ld +++ b/components/bootloader/subproject/main/ld/esp32/bootloader.ld @@ -74,6 +74,7 @@ SECTIONS .dram0.bss (NOLOAD) : { . = ALIGN (8); + _dram_start = ABSOLUTE(.); _bss_start = ABSOLUTE(.); *(.dynsbss) *(.sbss) @@ -150,6 +151,7 @@ SECTIONS *(.gnu.linkonce.lit4.*) _lit4_end = ABSOLUTE(.); . = ALIGN(4); + _dram_end = ABSOLUTE(.); } >dram_seg .iram.text : diff --git a/components/bootloader/subproject/main/ld/esp32s2/bootloader.ld b/components/bootloader/subproject/main/ld/esp32s2/bootloader.ld index b6e26c82e..1cced512c 100644 --- a/components/bootloader/subproject/main/ld/esp32s2/bootloader.ld +++ b/components/bootloader/subproject/main/ld/esp32s2/bootloader.ld @@ -59,6 +59,7 @@ SECTIONS .dram0.bss (NOLOAD) : { . = ALIGN (8); + _dram_start = ABSOLUTE(.); _bss_start = ABSOLUTE(.); *(.dynsbss) *(.sbss) @@ -135,7 +136,7 @@ SECTIONS *(.gnu.linkonce.lit4.*) _lit4_end = ABSOLUTE(.); . = ALIGN(4); - _heap_start = ABSOLUTE(.); + _dram_end = ABSOLUTE(.); } >dram_seg .iram.text : diff --git a/components/bootloader_support/include/esp_secure_boot.h b/components/bootloader_support/include/esp_secure_boot.h index 59b81df14..551a73705 100644 --- a/components/bootloader_support/include/esp_secure_boot.h +++ b/components/bootloader_support/include/esp_secure_boot.h @@ -25,6 +25,8 @@ #include "esp32/rom/secure_boot.h" #endif +typedef struct ets_secure_boot_signature ets_secure_boot_signature_t; + #ifdef CONFIG_SECURE_BOOT_V1_ENABLED #if !defined(CONFIG_SECURE_SIGNED_ON_BOOT) || !defined(CONFIG_SECURE_SIGNED_ON_UPDATE) || !defined(CONFIG_SECURE_SIGNED_APPS) #error "internal sdkconfig error, secure boot should always enable all signature options" @@ -149,6 +151,9 @@ esp_err_t esp_secure_boot_v2_permanently_enable(const esp_image_metadata_t *imag * * If flash encryption is enabled, the image will be transparently decrypted while being verified. * + * @note This function doesn't have any fault injection resistance so should not be called + * during a secure boot itself (but can be called when verifying an update, etc.) + * * @return ESP_OK if signature is valid, ESP_ERR_INVALID_STATE if * signature fails, ESP_FAIL for other failures (ie can't read flash). */ @@ -160,22 +165,43 @@ typedef struct { uint8_t signature[64]; } esp_secure_boot_sig_block_t; -/** @brief Verify the secure boot signature block. - * - * For ECDSA Scheme (Secure Boot V1) - Deterministic ECDSA w/ SHA256 based on the SHA256 hash of the image. - * For RSA Scheme (Secure Boot V2) - RSA-PSS Verification of the SHA-256 image based on the public key - * in the signature block. +/** @brief Verify the ECDSA secure boot signature block for Secure Boot V1. + * + * Calculates Deterministic ECDSA w/ SHA256 based on the SHA256 hash of the image. ECDSA signature + * verification must be enabled in project configuration to use this function. * * Similar to esp_secure_boot_verify_signature(), but can be used when the digest is precalculated. - * @param sig_block Pointer to RSA or ECDSA signature block data + * @param sig_block Pointer to ECDSA signature block data * @param image_digest Pointer to 32 byte buffer holding SHA-256 hash. + * @param verified_digest Pointer to 32 byte buffer that will receive verified digest if verification completes. (Used during bootloader implementation only, result is invalid otherwise.) * */ -#ifdef CONFIG_SECURE_SIGNED_APPS_ECDSA_SCHEME -esp_err_t esp_secure_boot_verify_signature_block(const esp_secure_boot_sig_block_t *sig_block, const uint8_t *image_digest); -#elif CONFIG_SECURE_SIGNED_APPS_RSA_SCHEME -esp_err_t esp_secure_boot_verify_signature_block(const ets_secure_boot_signature_t *sig_block, const uint8_t *image_digest); -#endif +esp_err_t esp_secure_boot_verify_ecdsa_signature_block(const esp_secure_boot_sig_block_t *sig_block, const uint8_t *image_digest, uint8_t *verified_digest); + + +/** @brief Verify the RSA secure boot signature block for Secure Boot V2. + * + * Performs RSA-PSS Verification of the SHA-256 image based on the public key + * in the signature block, compared against the public key digest stored in efuse. + * + * Similar to esp_secure_boot_verify_signature(), but can be used when the digest is precalculated. + * @param sig_block Pointer to RSA signature block data + * @param image_digest Pointer to 32 byte buffer holding SHA-256 hash. + * @param verified_digest Pointer to 32 byte buffer that will receive verified digest if verification completes. (Used during bootloader implementation only, result is invalid otherwise.) + * + */ +esp_err_t esp_secure_boot_verify_rsa_signature_block(const ets_secure_boot_signature_t *sig_block, const uint8_t *image_digest, uint8_t *verified_digest); + +/** @brief Legacy ECDSA verification function + * + * @note Deprecated, call either esp_secure_boot_verify_ecdsa_signature_block() or esp_secure_boot_verify_rsa_signature_block() instead. + * + * @param sig_block Pointer to ECDSA signature block data + * @param image_digest Pointer to 32 byte buffer holding SHA-256 hash. + */ +esp_err_t esp_secure_boot_verify_signature_block(const esp_secure_boot_sig_block_t *sig_block, const uint8_t *image_digest) + __attribute__((deprecated("use esp_secure_boot_verify_ecdsa_signature_block instead"))); + #define FLASH_OFFS_SECURE_BOOT_IV_DIGEST 0 diff --git a/components/bootloader_support/src/esp32/flash_encrypt.c b/components/bootloader_support/src/esp32/flash_encrypt.c index 8c78ff40f..2d900fe00 100644 --- a/components/bootloader_support/src/esp32/flash_encrypt.c +++ b/components/bootloader_support/src/esp32/flash_encrypt.c @@ -366,4 +366,4 @@ esp_err_t esp_flash_encrypt_region(uint32_t src_addr, size_t data_length) flash_failed: ESP_LOGE(TAG, "flash operation failed: 0x%x", err); return err; -} \ No newline at end of file +} diff --git a/components/bootloader_support/src/esp32/secure_boot.c b/components/bootloader_support/src/esp32/secure_boot.c index 0a2c573b2..6e6462138 100644 --- a/components/bootloader_support/src/esp32/secure_boot.c +++ b/components/bootloader_support/src/esp32/secure_boot.c @@ -263,7 +263,7 @@ static esp_err_t secure_boot_v2_digest_generate(uint32_t flash_offset, uint32_t /* Validating Signature block */ ret = validate_signature_block(sig_block, image_digest); if (ret != ESP_OK) { - ESP_LOGE(TAG, "signature block validation failed %d", ret); + ESP_LOGE(TAG, "signature block (address 0x%x) validation failed %d", sig_block_addr, ret); goto done; } @@ -329,7 +329,7 @@ esp_err_t esp_secure_boot_v2_permanently_enable(const esp_image_metadata_t *imag && REG_READ(EFUSE_BLK2_RDATA6_REG) == 0 && REG_READ(EFUSE_BLK2_RDATA7_REG) == 0) { /* Verifies the signature block appended to the image matches with the signature block of the app to be loaded */ - ret = secure_boot_v2_digest_generate(bootloader_data.start_addr, bootloader_data.image_len, boot_pub_key_digest); + ret = secure_boot_v2_digest_generate(bootloader_data.start_addr, bootloader_data.image_len - SIG_BLOCK_PADDING, boot_pub_key_digest); if (ret != ESP_OK) { ESP_LOGE(TAG, "Public key digest generation failed"); return ret; diff --git a/components/bootloader_support/src/esp32/secure_boot_signatures.c b/components/bootloader_support/src/esp32/secure_boot_signatures.c index 00ecdcdfb..30cebbcf5 100644 --- a/components/bootloader_support/src/esp32/secure_boot_signatures.c +++ b/components/bootloader_support/src/esp32/secure_boot_signatures.c @@ -20,8 +20,9 @@ #include "esp_image_format.h" #include "esp_secure_boot.h" #include "esp_spi_flash.h" +#include "esp_fault.h" #include "esp32/rom/sha.h" -#include "uECC.h" +#include "uECC_verify_antifault.h" #include #include @@ -38,10 +39,11 @@ extern const uint8_t signature_verification_key_end[] asm("_binary_signature_ver esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length) { uint8_t digest[DIGEST_LEN]; + uint8_t verified_digest[DIGEST_LEN] = { 0 }; /* ignored in this function */ const esp_secure_boot_sig_block_t *sigblock; ESP_LOGD(TAG, "verifying signature src_addr 0x%x length 0x%x", src_addr, length); - + esp_err_t err = bootloader_sha256_flash_contents(src_addr, length, digest); if (err != ESP_OK) { return err; @@ -54,7 +56,7 @@ esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length) return ESP_FAIL; } // Verify the signature - err = esp_secure_boot_verify_signature_block(sigblock, digest); + err = esp_secure_boot_verify_ecdsa_signature_block(sigblock, digest, verified_digest); // Unmap bootloader_munmap(sigblock); @@ -62,6 +64,12 @@ esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length) } esp_err_t esp_secure_boot_verify_signature_block(const esp_secure_boot_sig_block_t *sig_block, const uint8_t *image_digest) +{ + uint8_t verified_digest[DIGEST_LEN] = { 0 }; + return esp_secure_boot_verify_ecdsa_signature_block(sig_block, image_digest, verified_digest); +} + +esp_err_t esp_secure_boot_verify_ecdsa_signature_block(const esp_secure_boot_sig_block_t *sig_block, const uint8_t *image_digest, uint8_t *verified_digest) { ptrdiff_t keylen; @@ -79,12 +87,14 @@ esp_err_t esp_secure_boot_verify_signature_block(const esp_secure_boot_sig_block ESP_LOGD(TAG, "Verifying secure boot signature"); bool is_valid; - is_valid = uECC_verify(signature_verification_key_start, + is_valid = uECC_verify_antifault(signature_verification_key_start, image_digest, DIGEST_LEN, sig_block->signature, - uECC_secp256r1()); + uECC_secp256r1(), + verified_digest); ESP_LOGD(TAG, "Verification result %d", is_valid); + return is_valid ? ESP_OK : ESP_ERR_IMAGE_INVALID; } @@ -94,6 +104,7 @@ esp_err_t esp_secure_boot_verify_signature_block(const esp_secure_boot_sig_block esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length) { uint8_t digest[DIGEST_LEN] = {0}; + uint8_t verified_digest[DIGEST_LEN] = {0}; // ignored in this function const uint8_t *data; /* Padding to round off the input to the nearest 4k boundary */ @@ -115,7 +126,7 @@ esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length) } const ets_secure_boot_signature_t *sig_block = (const ets_secure_boot_signature_t *)(data + padded_length); - err = esp_secure_boot_verify_signature_block(sig_block, digest); + err = esp_secure_boot_verify_rsa_signature_block(sig_block, digest, verified_digest); if (err != ESP_OK) { ESP_LOGE(TAG, "Secure Boot V2 verification failed."); } @@ -124,16 +135,16 @@ esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length) return err; } -esp_err_t esp_secure_boot_verify_signature_block(const ets_secure_boot_signature_t *sig_block, const uint8_t *image_digest) +esp_err_t esp_secure_boot_verify_rsa_signature_block(const ets_secure_boot_signature_t *sig_block, const uint8_t *image_digest, uint8_t *verified_digest) { - uint8_t efuse_trusted_digest[DIGEST_LEN] = {0}, sig_block_trusted_digest[DIGEST_LEN] = {0}, verified_digest[DIGEST_LEN] = {0}; + uint8_t efuse_trusted_digest[DIGEST_LEN] = {0}, sig_block_trusted_digest[DIGEST_LEN] = {0}; secure_boot_v2_status_t r; memcpy(efuse_trusted_digest, (uint8_t *)EFUSE_BLK2_RDATA0_REG, DIGEST_LEN); /* EFUSE_BLK2_RDATA0_REG - Stores the Secure Boot Public Key Digest */ - + if (!ets_use_secure_boot_v2()) { ESP_LOGI(TAG, "Secure Boot EFuse bit(ABS_DONE_1) not yet programmed."); - + /* Generating the SHA of the public key components in the signature block */ bootloader_sha256_handle_t sig_block_sha; sig_block_sha = bootloader_sha256_start(); @@ -155,4 +166,4 @@ esp_err_t esp_secure_boot_verify_signature_block(const ets_secure_boot_signature return (r == SBV2_SUCCESS) ? ESP_OK : ESP_ERR_IMAGE_INVALID; } -#endif \ No newline at end of file +#endif diff --git a/components/bootloader_support/src/esp32s2/secure_boot_signatures.c b/components/bootloader_support/src/esp32s2/secure_boot_signatures.c index c8ba9a0b5..8f7b99486 100644 --- a/components/bootloader_support/src/esp32s2/secure_boot_signatures.c +++ b/components/bootloader_support/src/esp32s2/secure_boot_signatures.c @@ -27,6 +27,7 @@ esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length) { ets_secure_boot_key_digests_t trusted_keys = { 0 }; uint8_t digest[DIGEST_LEN]; + uint8_t verified_digest[DIGEST_LEN] = { 0 }; /* Note: this function doesn't do any anti-FI checks on this buffer */ const uint8_t *data; ESP_LOGD(TAG, "verifying signature src_addr 0x%x length 0x%x", src_addr, length); @@ -57,14 +58,14 @@ esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length) if (r == ETS_OK) { const ets_secure_boot_signature_t *sig = (const ets_secure_boot_signature_t *)(data + length); // TODO: calling this function in IDF app context is unsafe - r = ets_secure_boot_verify_signature(sig, digest, &trusted_keys); + r = ets_secure_boot_verify_signature(sig, digest, &trusted_keys, verified_digest); } bootloader_munmap(data); return (r == ETS_OK) ? ESP_OK : ESP_FAIL; } -esp_err_t esp_secure_boot_verify_signature_block(const ets_secure_boot_signature_t *sig_block, const uint8_t *image_digest) +esp_err_t esp_secure_boot_verify_rsa_signature_block(const ets_secure_boot_signature_t *sig_block, const uint8_t *image_digest, uint8_t *verified_digest) { ets_secure_boot_key_digests_t trusted_keys; @@ -74,7 +75,7 @@ esp_err_t esp_secure_boot_verify_signature_block(const ets_secure_boot_signature } else { ESP_LOGD(TAG, "Verifying with RSA-PSS..."); // TODO: calling this function in IDF app context is unsafe - r = ets_secure_boot_verify_signature(sig_block, image_digest, &trusted_keys); + r = ets_secure_boot_verify_signature(sig_block, image_digest, &trusted_keys, verified_digest); } return (r == 0) ? ESP_OK : ESP_ERR_IMAGE_INVALID; diff --git a/components/bootloader_support/src/esp_image_format.c b/components/bootloader_support/src/esp_image_format.c index 61ea3f7b3..933e7cacf 100644 --- a/components/bootloader_support/src/esp_image_format.c +++ b/components/bootloader_support/src/esp_image_format.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,7 @@ #include #include "bootloader_util.h" #include "bootloader_common.h" +#include "soc/soc_memory_layout.h" #if CONFIG_IDF_TARGET_ESP32 #include "esp32/rom/rtc.h" #include "esp32/rom/secure_boot.h" @@ -37,11 +39,11 @@ */ #ifdef BOOTLOADER_BUILD #ifdef CONFIG_SECURE_SIGNED_ON_BOOT -#define SECURE_BOOT_CHECK_SIGNATURE +#define SECURE_BOOT_CHECK_SIGNATURE 1 #endif #else /* !BOOTLOADER_BUILD */ #ifdef CONFIG_SECURE_SIGNED_ON_UPDATE -#define SECURE_BOOT_CHECK_SIGNATURE +#define SECURE_BOOT_CHECK_SIGNATURE 1 #endif #endif @@ -61,9 +63,6 @@ static const char *TAG = "esp_image"; */ static uint32_t ram_obfs_value[2]; -/* Range of IRAM used by the loader, defined in ld script */ -extern int _loader_text_start; -extern int _loader_text_end; #endif /* Return true if load_addr is an address the bootloader should load into */ @@ -94,7 +93,7 @@ static esp_err_t verify_segment_header(int index, const esp_image_segment_header static esp_err_t verify_checksum(bootloader_sha256_handle_t sha_handle, uint32_t checksum_word, esp_image_metadata_t *data); -static esp_err_t __attribute__((unused)) verify_secure_boot_signature(bootloader_sha256_handle_t sha_handle, esp_image_metadata_t *data); +static esp_err_t __attribute__((unused)) verify_secure_boot_signature(bootloader_sha256_handle_t sha_handle, esp_image_metadata_t *data, uint8_t *image_digest, uint8_t *verified_digest); static esp_err_t __attribute__((unused)) verify_simple_hash(bootloader_sha256_handle_t sha_handle, esp_image_metadata_t *data); static esp_err_t image_load(esp_image_load_mode_t mode, const esp_partition_pos_t *part, esp_image_metadata_t *data) @@ -112,6 +111,11 @@ static esp_err_t image_load(esp_image_load_mode_t mode, const esp_partition_pos_ uint32_t checksum_word = ESP_ROM_CHECKSUM_INITIAL; uint32_t *checksum = NULL; bootloader_sha256_handle_t sha_handle = NULL; +#if SECURE_BOOT_CHECK_SIGNATURE + /* used for anti-FI checks */ + uint8_t image_digest[HASH_LEN] = { [ 0 ... 31] = 0xEE }; + uint8_t verified_digest[HASH_LEN] = { [ 0 ... 31 ] = 0x01 }; +#endif if (data == NULL || part == NULL) { return ESP_ERR_INVALID_ARG; @@ -169,6 +173,7 @@ static esp_err_t image_load(esp_image_load_mode_t mode, const esp_partition_pos_ for (int i = 0; i < data->image.segment_count; i++) { esp_image_segment_header_t *header = &data->segments[i]; ESP_LOGV(TAG, "loading segment header %d at offset 0x%x", i, next_addr); + err = process_segment(i, next_addr, header, silent, do_load, sha_handle, checksum); if (err != ESP_OK) { goto err; @@ -194,14 +199,14 @@ static esp_err_t image_load(esp_image_load_mode_t mode, const esp_partition_pos_ } } - /* For secure boot on ESP32, we don't calculate SHA or verify signautre on bootloaders. - For ESP32S2, we do verify signature on bootloader which includes the SHA calculation. + /* For secure boot V1 on ESP32, we don't calculate SHA or verify signature on bootloaders. + For Secure Boot V2, we do verify signature on bootloader which includes the SHA calculation. (For non-secure boot, we don't verify any SHA-256 hash appended to the bootloader because esptool.py may have rewritten the header - rely on esptool.py having verified the bootloader at flashing time, instead.) */ bool verify_sha; -#if CONFIG_SECURE_BOOT_V2_ENABLED && CONFIG_IDF_TARGET_ESP32S2 +#if CONFIG_SECURE_BOOT_V2_ENABLED verify_sha = true; #else // ESP32, or ESP32S2 without secure boot enabled verify_sha = (data->start_addr != ESP_BOOTLOADER_OFFSET); @@ -214,7 +219,7 @@ static esp_err_t image_load(esp_image_load_mode_t mode, const esp_partition_pos_ #ifdef SECURE_BOOT_CHECK_SIGNATURE // secure boot images have a signature appended - err = verify_secure_boot_signature(sha_handle, data); + err = verify_secure_boot_signature(sha_handle, data, image_digest, verified_digest); #else // No secure boot, but SHA-256 can be appended for basic corruption detection if (sha_handle != NULL && !esp_cpu_in_ocd_debug_mode()) { @@ -247,7 +252,28 @@ static esp_err_t image_load(esp_image_load_mode_t mode, const esp_partition_pos_ } #ifdef BOOTLOADER_BUILD - if (do_load && ram_obfs_value[0] != 0 && ram_obfs_value[1] != 0) { // Need to deobfuscate RAM + +#ifdef SECURE_BOOT_CHECK_SIGNATURE + /* If signature was checked in bootloader build, verified_digest should equal image_digest + + This is to detect any fault injection that caused signature verification to not complete normally. + + Any attack which bypasses this check should be of limited use as the RAM contents are still obfuscated, therefore we do the check + immediately before we deobfuscate. + + Note: the conditions for making this check are the same as for setting verify_sha above, but on ESP32 SB V1 we move the test for + "only verify signature in bootloader" into the macro so it's tested multiple times. + */ +#if CONFIG_SECURE_BOOT_V2_ENABLED + ESP_FAULT_ASSERT(memcmp(image_digest, verified_digest, HASH_LEN) == 0); +#else // Secure Boot V1 on ESP32, only verify signatures for apps not bootloaders + ESP_FAULT_ASSERT(data->start_addr == ESP_BOOTLOADER_OFFSET || memcmp(image_digest, verified_digest, HASH_LEN) == 0); +#endif + +#endif // SECURE_BOOT_CHECK_SIGNATURE + + // Deobfuscate RAM + if (do_load && ram_obfs_value[0] != 0 && ram_obfs_value[1] != 0) { for (int i = 0; i < data->image.segment_count; i++) { uint32_t load_addr = data->segments[i].load_addr; if (should_load(load_addr)) { @@ -333,6 +359,127 @@ static esp_err_t verify_image_header(uint32_t src_addr, const esp_image_header_t return err; } +#ifdef BOOTLOADER_BUILD +/* Check the region load_addr - load_end doesn't overlap any memory used by the bootloader, registers, or other invalid memory + */ +static bool verify_load_addresses(int segment_index, intptr_t load_addr, intptr_t load_end, bool print_error, bool no_recurse) +{ + /* Addresses of static data and the "loader" section of bootloader IRAM, all defined in ld script */ + const char *reason = NULL; + extern int _dram_start, _dram_end, _loader_text_start, _loader_text_end; + void *load_addr_p = (void *)load_addr; + void *load_end_p = (void *)load_end; + + if (load_end == load_addr) { + return true; // zero-length segments are fine + } + assert(load_end > load_addr); // data_len<16MB is checked in verify_segment_header() which is called before this, so this should always be true + + if (esp_ptr_in_dram(load_addr_p) && esp_ptr_in_dram(load_end_p)) { /* Writing to DRAM */ + /* Check if we're clobbering the stack */ + intptr_t sp = (intptr_t)get_sp(); + if (bootloader_util_regions_overlap(sp - STACK_LOAD_HEADROOM, SOC_ROM_STACK_START, + load_addr, load_end)) { + reason = "overlaps bootloader stack"; + goto invalid; + } + + /* Check if we're clobbering static data + + (_dram_start.._dram_end includes bss, data, rodata sections in DRAM) + */ + if (bootloader_util_regions_overlap((intptr_t)&_dram_start, (intptr_t)&_dram_end, load_addr, load_end)) { + reason = "overlaps bootloader data"; + goto invalid; + } + + /* LAST DRAM CHECK (recursive): for D/IRAM, check the equivalent IRAM addresses if needed + + Allow for the possibility that even though both pointers are IRAM, only part of the region is in a D/IRAM + section. In which case we recurse to check the part which falls in D/IRAM. + + Note: We start with SOC_DIRAM_DRAM_LOW/HIGH and convert that address to IRAM to account for any reversing of word order + (chip-specific). + */ + if (!no_recurse && bootloader_util_regions_overlap(SOC_DIRAM_DRAM_LOW, SOC_DIRAM_DRAM_HIGH, load_addr, load_end)) { + intptr_t iram_load_addr, iram_load_end; + + if (esp_ptr_in_diram_dram(load_addr_p)) { + iram_load_addr = (intptr_t)esp_ptr_diram_dram_to_iram(load_addr_p); + } else { + iram_load_addr = (intptr_t)esp_ptr_diram_dram_to_iram((void *)SOC_DIRAM_DRAM_LOW); + } + + if (esp_ptr_in_diram_dram(load_end_p)) { + iram_load_end = (intptr_t)esp_ptr_diram_dram_to_iram(load_end_p); + } else { + iram_load_end = (intptr_t)esp_ptr_diram_dram_to_iram((void *)SOC_DIRAM_DRAM_HIGH); + } + + if (iram_load_end < iram_load_addr) { + return verify_load_addresses(segment_index, iram_load_end, iram_load_addr, print_error, true); + } else { + return verify_load_addresses(segment_index, iram_load_addr, iram_load_end, print_error, true); + } + } + } + else if (esp_ptr_in_iram(load_addr_p) && esp_ptr_in_iram(load_end_p)) { /* Writing to IRAM */ + /* Check for overlap of 'loader' section of IRAM */ + if (bootloader_util_regions_overlap((intptr_t)&_loader_text_start, (intptr_t)&_loader_text_end, + load_addr, load_end)) { + reason = "overlaps loader IRAM"; + goto invalid; + } + + /* LAST IRAM CHECK (recursive): for D/IRAM, check the equivalent DRAM address if needed + + Allow for the possibility that even though both pointers are IRAM, only part of the region is in a D/IRAM + section. In which case we recurse to check the part which falls in D/IRAM. + Note: We start with SOC_DIRAM_IRAM_LOW/HIGH and convert that address to DRAM to account for any reversing of word order + (chip-specific). + */ + if (!no_recurse && bootloader_util_regions_overlap(SOC_DIRAM_IRAM_LOW, SOC_DIRAM_IRAM_HIGH, load_addr, load_end)) { + intptr_t dram_load_addr, dram_load_end; + + if (esp_ptr_in_diram_iram(load_addr_p)) { + dram_load_addr = (intptr_t)esp_ptr_diram_iram_to_dram(load_addr_p); + } else { + dram_load_addr = (intptr_t)esp_ptr_diram_iram_to_dram((void *)SOC_DIRAM_IRAM_LOW); + } + + if (esp_ptr_in_diram_iram(load_end_p)) { + dram_load_end = (intptr_t)esp_ptr_diram_iram_to_dram(load_end_p); + } else { + dram_load_end = (intptr_t)esp_ptr_diram_iram_to_dram((void *)SOC_DIRAM_IRAM_HIGH); + } + + if (dram_load_end < dram_load_addr) { + return verify_load_addresses(segment_index, dram_load_end, dram_load_addr, print_error, true); + } else { + return verify_load_addresses(segment_index, dram_load_addr, dram_load_end, print_error, true); + } + } + /* Sections entirely in RTC memory won't overlap with a vanilla bootloader but are valid load addresses, thus skipping them from the check */ + } else if (esp_ptr_in_rtc_iram_fast(load_addr_p) && esp_ptr_in_rtc_iram_fast(load_end_p)){ + return true; + } else if (esp_ptr_in_rtc_dram_fast(load_addr_p) && esp_ptr_in_rtc_dram_fast(load_end_p)){ + return true; + } else if (esp_ptr_in_rtc_slow(load_addr_p) && esp_ptr_in_rtc_slow(load_end_p)) { + return true; + } else { /* Not a DRAM or an IRAM or RTC Fast IRAM, RTC Fast DRAM or RTC Slow address */ + reason = "bad load address range"; + goto invalid; + } + return true; + + invalid: + if (print_error) { + ESP_LOGE(TAG, "Segment %d 0x%08x-0x%08x invalid: %s", segment_index, load_addr, load_end, reason); + } + return false; +} +#endif // BOOTLOADER_BUILD + static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segment_header_t *header, bool silent, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum) { esp_err_t err; @@ -376,33 +523,8 @@ static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segme #ifdef BOOTLOADER_BUILD /* Before loading segment, check it doesn't clobber bootloader RAM. */ if (do_load && data_len > 0) { - const intptr_t load_end = load_addr + data_len; - if (load_end < (intptr_t) SOC_DRAM_HIGH) { - /* Writing to DRAM */ - intptr_t sp = (intptr_t)get_sp(); - if (load_end > sp - STACK_LOAD_HEADROOM) { - /* Bootloader .data/.rodata/.bss is above the stack, so this - * also checks that we aren't overwriting these segments. - * - * TODO: This assumes specific arrangement of sections we have - * in the ESP32. Rewrite this in a generic way to support other - * layouts. - */ - ESP_LOGE(TAG, "Segment %d end address 0x%08x too high (bootloader stack 0x%08x limit 0x%08x)", - index, load_end, sp, sp - STACK_LOAD_HEADROOM); - return ESP_ERR_IMAGE_INVALID; - } - } else { - /* Writing to IRAM */ - const intptr_t loader_iram_start = (intptr_t) &_loader_text_start; - const intptr_t loader_iram_end = (intptr_t) &_loader_text_end; - - if (bootloader_util_regions_overlap(loader_iram_start, loader_iram_end, - load_addr, load_end)) { - ESP_LOGE(TAG, "Segment %d (0x%08x-0x%08x) overlaps bootloader IRAM (0x%08x-0x%08x)", - index, load_addr, load_end, loader_iram_start, loader_iram_end); - return ESP_ERR_IMAGE_INVALID; - } + if (!verify_load_addresses(index, load_addr, load_addr + data_len, true, false)) { + return ESP_ERR_IMAGE_INVALID; } } #endif // BOOTLOADER_BUILD @@ -412,6 +534,10 @@ static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segme int32_t data_len_remain = data_len; while (data_len_remain > 0) { +#if SECURE_BOOT_CHECK_SIGNATURE && defined(BOOTLOADER_BUILD) + /* Double check the address verification done above */ + ESP_FAULT_ASSERT(!do_load || verify_load_addresses(0, load_addr, load_addr + data_len_remain, false, false)); +#endif uint32_t offset_page = ((data_addr & MMAP_ALIGNED_MASK) != 0) ? 1 : 0; /* Data we could map in case we are not aligned to PAGE boundary is one page size lesser. */ data_len = MIN(data_len_remain, ((free_page_count - offset_page) * SPI_FLASH_MMU_PAGE_SIZE)); @@ -437,7 +563,7 @@ static esp_err_t process_segment_data(intptr_t load_addr, uint32_t data_addr, ui { // If we are not loading, and the checksum is empty, skip processing this // segment for data - if(!do_load && checksum == NULL) { + if (!do_load && checksum == NULL) { ESP_LOGD(TAG, "skipping checksum for segment"); return ESP_OK; } @@ -620,9 +746,9 @@ static esp_err_t verify_checksum(bootloader_sha256_handle_t sha_handle, uint32_t return ESP_OK; } -static esp_err_t verify_secure_boot_signature(bootloader_sha256_handle_t sha_handle, esp_image_metadata_t *data) +static esp_err_t verify_secure_boot_signature(bootloader_sha256_handle_t sha_handle, esp_image_metadata_t *data, uint8_t *image_digest, uint8_t *verified_digest) { - uint8_t image_hash[HASH_LEN] = { 0 }; +#ifdef SECURE_BOOT_CHECK_SIGNATURE uint32_t end = data->start_addr + data->image_len; ESP_LOGI(TAG, "Verifying image signature..."); @@ -634,21 +760,37 @@ static esp_err_t verify_secure_boot_signature(bootloader_sha256_handle_t sha_han bootloader_sha256_data(sha_handle, simple_hash, HASH_LEN); bootloader_munmap(simple_hash); } - bootloader_sha256_finish(sha_handle, image_hash); + +#if CONFIG_SECURE_SIGNED_APPS_RSA_SCHEME + // End of the image needs to be padded all the way to a 4KB boundary, after the simple hash + // (for apps they are usually already padded due to --secure-pad-v2, only a problem if this option was not used.) + uint32_t padded_end = (end + FLASH_SECTOR_SIZE - 1) & ~(FLASH_SECTOR_SIZE-1); + if (padded_end > end) { + const void *padding = bootloader_mmap(end, padded_end - end); + bootloader_sha256_data(sha_handle, padding, padded_end - end); + bootloader_munmap(padding); + end = padded_end; + } +#endif + + bootloader_sha256_finish(sha_handle, image_digest); // Log the hash for debugging - bootloader_debug_buffer(image_hash, HASH_LEN, "Calculated secure boot hash"); + bootloader_debug_buffer(image_digest, HASH_LEN, "Calculated secure boot hash"); -#ifdef SECURE_BOOT_CHECK_SIGNATURE // Use hash to verify signature block esp_err_t err = ESP_ERR_IMAGE_INVALID; const void *sig_block; - #ifdef CONFIG_SECURE_SIGNED_APPS_ECDSA_SCHEME +#ifdef CONFIG_SECURE_SIGNED_APPS_ECDSA_SCHEME + ESP_FAULT_ASSERT(memcmp(image_digest, verified_digest, HASH_LEN) != 0); /* sanity check that these values start differently */ sig_block = bootloader_mmap(data->start_addr + data->image_len, sizeof(esp_secure_boot_sig_block_t)); - #elif CONFIG_SECURE_SIGNED_APPS_RSA_SCHEME + err = esp_secure_boot_verify_ecdsa_signature_block(sig_block, image_digest, verified_digest); +#elif CONFIG_SECURE_SIGNED_APPS_RSA_SCHEME + ESP_FAULT_ASSERT(memcmp(image_digest, verified_digest, HASH_LEN) != 0); /* sanity check that these values start differently */ sig_block = bootloader_mmap(end, sizeof(ets_secure_boot_signature_t)); - #endif - err = esp_secure_boot_verify_signature_block(sig_block, image_hash); + err = esp_secure_boot_verify_rsa_signature_block(sig_block, image_digest, verified_digest); +#endif + bootloader_munmap(sig_block); if (err != ESP_OK) { ESP_LOGE(TAG, "Secure boot signature verification failed"); @@ -668,13 +810,13 @@ static esp_err_t verify_secure_boot_signature(bootloader_sha256_handle_t sha_han } return ESP_ERR_IMAGE_INVALID; } -#endif #if CONFIG_SECURE_SIGNED_APPS_RSA_SCHEME // Adjust image length result to include the appended signature data->image_len = end - data->start_addr + sizeof(ets_secure_boot_signature_t); #endif +#endif // SECURE_BOOT_CHECK_SIGNATURE return ESP_OK; } diff --git a/components/bootloader_support/src/idf/secure_boot_signatures.c b/components/bootloader_support/src/idf/secure_boot_signatures.c index 1521c253c..aae4599b7 100644 --- a/components/bootloader_support/src/idf/secure_boot_signatures.c +++ b/components/bootloader_support/src/idf/secure_boot_signatures.c @@ -41,6 +41,7 @@ extern const uint8_t signature_verification_key_end[] asm("_binary_signature_ver esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length) { uint8_t digest[DIGEST_LEN]; + uint8_t verified_digest[DIGEST_LEN]; const esp_secure_boot_sig_block_t *sigblock; ESP_LOGD(TAG, "verifying signature src_addr 0x%x length 0x%x", src_addr, length); @@ -57,12 +58,12 @@ esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length) ESP_LOGE(TAG, "bootloader_mmap(0x%x, 0x%x) failed", src_addr + length, sizeof(esp_secure_boot_sig_block_t)); return ESP_FAIL; } - err = esp_secure_boot_verify_signature_block(sigblock, digest); + err = esp_secure_boot_verify_ecdsa_signature_block(sigblock, digest, verified_digest); bootloader_munmap(sigblock); return err; } -esp_err_t esp_secure_boot_verify_signature_block(const esp_secure_boot_sig_block_t *sig_block, const uint8_t *image_digest) +esp_err_t esp_secure_boot_verify_ecdsa_signature_block(const esp_secure_boot_sig_block_t *sig_block, const uint8_t *image_digest, uint8_t *verified_digest) { #if !(defined(CONFIG_MBEDTLS_ECDSA_C) && defined(CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED)) ESP_LOGE(TAG, "Signature verification requires ECDSA & SECP256R1 curve enabled"); @@ -70,6 +71,9 @@ esp_err_t esp_secure_boot_verify_signature_block(const esp_secure_boot_sig_block #else ptrdiff_t keylen; + /* Note: in IDF app image verification we don't add any fault injection resistance, boot-time checks only */ + memset(verified_digest, 0, DIGEST_LEN); + keylen = signature_verification_key_end - signature_verification_key_start; if (keylen != SIGNATURE_VERIFICATION_KEYLEN) { ESP_LOGE(TAG, "Embedded public verification key has wrong length %d", keylen); @@ -141,9 +145,10 @@ static const char *TAG = "secure_boot_v2"; esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length) { uint8_t digest[DIGEST_LEN] = {0}; + uint8_t verified_digest[DIGEST_LEN] = {0}; /* Rounding off length to the upper 4k boundary */ - int padded_length = ALIGN_UP(length, FLASH_SECTOR_SIZE); + uint32_t padded_length = ALIGN_UP(length, FLASH_SECTOR_SIZE); ESP_LOGD(TAG, "verifying signature src_addr 0x%x length 0x%x", src_addr, length); esp_err_t err = bootloader_sha256_flash_contents(src_addr, padded_length, digest); @@ -158,7 +163,7 @@ esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length) return ESP_FAIL; } - err = esp_secure_boot_verify_signature_block(sig_block, digest); + err = esp_secure_boot_verify_rsa_signature_block(sig_block, digest, verified_digest); if (err != ESP_OK) { ESP_LOGE(TAG, "Secure Boot V2 verification failed."); } @@ -166,11 +171,15 @@ esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length) return err; } -esp_err_t esp_secure_boot_verify_signature_block(const ets_secure_boot_signature_t *sig_block, const uint8_t *image_digest) +esp_err_t esp_secure_boot_verify_rsa_signature_block(const ets_secure_boot_signature_t *sig_block, const uint8_t *image_digest, uint8_t *verified_digest) { uint8_t i = 0, efuse_trusted_digest[DIGEST_LEN] = {0}, sig_block_trusted_digest[DIGEST_LEN] = {0}; memcpy(efuse_trusted_digest, (uint8_t *) EFUSE_BLK2_RDATA0_REG, sizeof(efuse_trusted_digest)); + /* Note: in IDF verification we don't add any fault injection resistance, as we don't expect this to be called + during boot-time verification. */ + memset(verified_digest, 0, DIGEST_LEN); + /* Generating the SHA of the public key components in the signature block */ bootloader_sha256_handle_t sig_block_sha; sig_block_sha = bootloader_sha256_start(); diff --git a/components/esp_common/include/esp_fault.h b/components/esp_common/include/esp_fault.h new file mode 100644 index 000000000..0eb7ebb63 --- /dev/null +++ b/components/esp_common/include/esp_fault.h @@ -0,0 +1,93 @@ +// Copyright 2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "sdkconfig.h" +#include "soc/rtc_cntl_reg.h" + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Assert a condition is true, in a way that should be resistant to fault injection for + * single fault attacks. + * + * - Expands CONDITION multiple times (condition must have no side effects) + * - Compiler is told all registers are invalid before evaluating CONDITION each time, to avoid a fault + * causing a misread of a register used in all three evaluations of CONDITION. + * - If CONDITION is ever false, a system reset is triggered. + * + * @note Place this macro after a "normal" check of CONDITION that will fail with a normal error + * message. This is the fallback in case a fault injection attack skips or corrupts the result of + * that check. (Although ensure that an attacker can't use fault injection to skip past the "normal" + * error message, to avoid this check entirely.) + * + * @note This macro increases binary size and is slow and should be used sparingly. + * + * @note This macro does not guarantee fault injection resistance. In particular CONDITION must be + * chosen carefully - a fault injection attack which sets CONDITION to true will not be detected by + * this macro. Care must also be taken that an attacker can't use a fault to completely bypass calling + * whatever function tests ESP_FAULT_ASSERT. + * + * @note This is difficult to debug as a failure triggers an instant software reset, and UART output + * is often truncated (as FIFO is not flushed). Define the ESP_FAULT_ASSERT_DEBUG macro to debug any + * failures of this macro due to software bugs. + * + * @param CONDITION A condition which will evaluate true unless an attacker used fault injection to skip or corrupt some other critical system calculation. + * + */ +#define ESP_FAULT_ASSERT(CONDITION) do { \ + asm volatile ("" ::: "memory"); \ + if(!(CONDITION)) _ESP_FAULT_RESET(); \ + asm volatile ("" ::: "memory"); \ + if(!(CONDITION)) _ESP_FAULT_RESET(); \ + asm volatile ("" ::: "memory"); \ + if(!(CONDITION)) _ESP_FAULT_RESET(); \ +} while(0) + + +// Uncomment this macro to get debug output if ESP_FAULT_ASSERT() fails +// +// Note that uncommenting this macro reduces the anti-FI effectiveness +// +//#define ESP_FAULT_ASSERT_DEBUG + +/* Internal macro, purpose is to trigger a system reset if an inconsistency due to fault injection + is detected. + + Illegal instruction opcodes are there as a fallback to crash the CPU in case it doesn't + reset as expected. +*/ +#ifndef ESP_FAULT_ASSERT_DEBUG + +#define _ESP_FAULT_RESET() do { \ + REG_WRITE(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_SW_SYS_RST); \ + asm volatile("ill; ill; ill;"); \ + } while(0) + +#else // ESP_FAULT_ASSERT_DEBUG + +#warning "Enabling ESP_FAULT_ASSERT_DEBUG makes ESP_FAULT_ASSERT() less effective" + +#define _ESP_FAULT_RESET() do { \ + ets_printf("ESP_FAULT_ASSERT %s:%d\n", __FILE__, __LINE__); \ + asm volatile("ill;"); \ + } while(0) + +#endif // ESP_FAULT_ASSERT_DEBUG + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_rom/include/esp32s2/rom/secure_boot.h b/components/esp_rom/include/esp32s2/rom/secure_boot.h index 6ee9574bc..54ee3ce44 100644 --- a/components/esp_rom/include/esp32s2/rom/secure_boot.h +++ b/components/esp_rom/include/esp32s2/rom/secure_boot.h @@ -1,4 +1,4 @@ -// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// Copyright 2020 Espressif Systems (Shanghai) PTE LTD // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -29,45 +29,77 @@ typedef struct ets_secure_boot_sig_block ets_secure_boot_sig_block_t; typedef struct ets_secure_boot_signature ets_secure_boot_signature_t; typedef struct ets_secure_boot_key_digests ets_secure_boot_key_digests_t; -/* Verify bootloader image (reconfigures cache to map, - loads trusted key digests from efuse) +/* 64KB 'staging buffer' for loading the verified bootloader - If allow_key_revoke is true and aggressive revoke efuse is set, - any failed signature has its associated key revoked in efuse. + Comes from the "shared buffers" region (see shared_buffers.h) - If result is ETS_OK, the "simple hash" of the bootloader - is copied into verified_hash. + The bootloader can't be safely linked into this address range + (may be possible with some cleverness.) */ -int ets_secure_boot_verify_bootloader(uint8_t *verified_hash, bool allow_key_revoke); +#define SECURE_BOOT_STAGING_BUFFER_START ((uint32_t)(g_shared_buffers.secure_boot_staging_buf)) +#define SECURE_BOOT_STAGING_BUFFER_SZ sizeof(g_shared_buffers.secure_boot_staging_buf) +#define SECURE_BOOT_STAGING_BUFFER_END (SECURE_BOOT_STAGING_BUFFER_START + SECURE_BOOT_STAGING_BUFFER_SZ) -/* Verify bootloader image (reconfigures cache to map), with - key digests provided as parameters.) +/* Anti-FI measure: use full words for success/fail, instead of + 0/non-zero +*/ +typedef enum { + SB_SUCCESS = 0x3A5A5AA5, + SB_FAILED = 0x7533885E, +} ets_secure_boot_status_t; + + +/* Verify and stage-load the bootloader image + (reconfigures cache to map, loads trusted key digests from efuse, + copies the bootloader into the staging buffer.) + + If allow_key_revoke is true and aggressive revoke efuse is set, + any failed signature has its associated key revoked in efuse. + + If result is SB_SUCCESS, the "simple hash" of the bootloader + is copied into verified_hash. +*/ +ets_secure_boot_status_t ets_secure_boot_verify_stage_bootloader(uint8_t *verified_hash, bool allow_key_revoke); + +/* Verify bootloader image (reconfigures cache to map), + with key digests provided as parameters.) Can be used to verify secure boot status before enabling secure boot permanently. - If result is ETS_OK, the "simple hash" of the bootloader is + If stage_load parameter is true, bootloader is copied into staging + buffer in RAM at the same time. + + If result is SB_SUCCESS, the "simple hash" of the bootloader is copied into verified_hash. */ -int ets_secure_boot_verify_bootloader_with_keys(uint8_t *verified_hash, const ets_secure_boot_key_digests_t *trusted_keys); +ets_secure_boot_status_t ets_secure_boot_verify_bootloader_with_keys(uint8_t *verified_hash, const ets_secure_boot_key_digests_t *trusted_keys, bool stage_load); + +/* Read key digests from efuse. Any revoked/missing digests will be + marked as NULL +*/ +ETS_STATUS ets_secure_boot_read_key_digests(ets_secure_boot_key_digests_t *trusted_keys); /* Verify supplied signature against supplied digest, using supplied trusted key digests. - Doesn't reconfigure cache or any other hardware access. -*/ -int ets_secure_boot_verify_signature(const ets_secure_boot_signature_t *sig, const uint8_t *image_digest, const ets_secure_boot_key_digests_t *trusted_keys); + Doesn't reconfigure cache or any other hardware access except for RSA peripheral. -/* Read key digests from efuse. Any revoked/missing digests will be - marked as NULL - - Returns 0 if at least one valid digest was found. + If result is SB_SUCCESS, the image_digest value is copied into verified_digest. */ -int ets_secure_boot_read_key_digests(ets_secure_boot_key_digests_t *trusted_keys); +ets_secure_boot_status_t ets_secure_boot_verify_signature(const ets_secure_boot_signature_t *sig, const uint8_t *image_digest, const ets_secure_boot_key_digests_t *trusted_keys, uint8_t *verified_digest); + +/* Revoke a public key digest in efuse. + @param index Digest to revoke. Must be 0, 1 or 2. + */ +void ets_secure_boot_revoke_public_key_digest(int index); #define ETS_SECURE_BOOT_V2_SIGNATURE_MAGIC 0xE7 -/* Secure Boot V2 signature block (up to 3 can be appended) */ +/* Secure Boot V2 signature block + + (Up to 3 in a signature sector are appended to the image) + */ struct ets_secure_boot_sig_block { uint8_t magic_byte; uint8_t version; @@ -92,8 +124,10 @@ struct ets_secure_boot_signature { _Static_assert(sizeof(ets_secure_boot_signature_t) == 4096, "invalid sig sector size"); +#define MAX_KEY_DIGESTS 3 + struct ets_secure_boot_key_digests { - const void *key_digests[3]; + const void *key_digests[MAX_KEY_DIGESTS]; bool allow_key_revoke; }; diff --git a/components/heap/heap_caps.c b/components/heap/heap_caps.c index 1d73b0087..e97bc9d19 100644 --- a/components/heap/heap_caps.c +++ b/components/heap/heap_caps.c @@ -39,17 +39,16 @@ possible. This should optimize the amount of RAM accessible to the code without IRAM_ATTR static void *dram_alloc_to_iram_addr(void *addr, size_t len) { uintptr_t dstart = (uintptr_t)addr; //First word - uintptr_t dend = dstart + len; //Last word + 4 + uintptr_t dend = dstart + len - 4; //Last word assert(esp_ptr_in_diram_dram((void *)dstart)); assert(esp_ptr_in_diram_dram((void *)dend)); assert((dstart & 3) == 0); assert((dend & 3) == 0); -#if SOC_DIRAM_INVERTED - uint32_t istart = SOC_DIRAM_IRAM_LOW + (SOC_DIRAM_DRAM_HIGH - dend); +#ifdef SOC_DIRAM_INVERTED // We want the word before the result to hold the DRAM address + uint32_t *iptr = esp_ptr_diram_dram_to_iram((void *)dend); #else - uint32_t istart = SOC_DIRAM_IRAM_LOW + (dstart - SOC_DIRAM_DRAM_LOW); + uint32_t *iptr = esp_ptr_diram_dram_to_iram((void *)dstart); #endif - uint32_t *iptr = (uint32_t *)istart; *iptr = dstart; return iptr + 1; } diff --git a/components/heap/test/test_diram.c b/components/heap/test/test_diram.c new file mode 100644 index 000000000..179663521 --- /dev/null +++ b/components/heap/test/test_diram.c @@ -0,0 +1,74 @@ +/* + Tests for D/IRAM support in heap capability allocator +*/ + +#include +#include +#include "unity.h" +#include "esp_heap_caps.h" +#include "soc/soc_memory_layout.h" + +#define ALLOC_SZ 1024 + +static void *malloc_block_diram(uint32_t caps) +{ + void *attempts[256] = { 0 }; // Allocate up to 256 ALLOC_SZ blocks to exhaust all non-D/IRAM memory temporarily + int count = 0; + void *result; + + while(count < sizeof(attempts)/sizeof(void *)) { + result = heap_caps_malloc(ALLOC_SZ, caps); + TEST_ASSERT_NOT_NULL_MESSAGE(result, "not enough free heap to perform test"); + + if (esp_ptr_in_diram_dram(result) || esp_ptr_in_diram_iram(result)) { + break; + } + + attempts[count] = result; + result = NULL; + count++; + } + + for (int i = 0; i < count; i++) { + free(attempts[i]); + } + + TEST_ASSERT_NOT_NULL_MESSAGE(result, "not enough D/IRAM memory is free"); + return result; +} + +TEST_CASE("Allocate D/IRAM as DRAM", "[heap]") +{ + uint32_t *dram = malloc_block_diram(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + + for (int i = 0; i < ALLOC_SZ / sizeof(uint32_t); i++) { + uint32_t v = i + 0xAAAA; + dram[i] = v; + volatile uint32_t *iram = esp_ptr_diram_dram_to_iram(dram + i); + TEST_ASSERT_EQUAL(v, dram[i]); + TEST_ASSERT_EQUAL(v, *iram); + *iram = UINT32_MAX; + TEST_ASSERT_EQUAL(UINT32_MAX, *iram); + TEST_ASSERT_EQUAL(UINT32_MAX, dram[i]); + } + + free(dram); +} + +TEST_CASE("Allocate D/IRAM as IRAM", "[heap]") +{ + uint32_t *iram = malloc_block_diram(MALLOC_CAP_EXEC); + + for (int i = 0; i < ALLOC_SZ / sizeof(uint32_t); i++) { + uint32_t v = i + 0xEEE; + iram[i] = v; + volatile uint32_t *dram = esp_ptr_diram_iram_to_dram(iram + i); + TEST_ASSERT_EQUAL_HEX32(v, iram[i]); + TEST_ASSERT_EQUAL_HEX32(v, *dram); + *dram = UINT32_MAX; + TEST_ASSERT_EQUAL_HEX32(UINT32_MAX, *dram); + TEST_ASSERT_EQUAL_HEX32(UINT32_MAX, iram[i]); + } + + free(iram); +} diff --git a/components/soc/include/soc/soc_memory_layout.h b/components/soc/include/soc/soc_memory_layout.h index c1b0538f9..068890c8a 100644 --- a/components/soc/include/soc/soc_memory_layout.h +++ b/components/soc/include/soc/soc_memory_layout.h @@ -213,9 +213,47 @@ inline static bool IRAM_ATTR esp_ptr_in_diram_iram(const void *p) { return ((intptr_t)p >= SOC_DIRAM_IRAM_LOW && (intptr_t)p < SOC_DIRAM_IRAM_HIGH); } +inline static bool IRAM_ATTR esp_ptr_in_rtc_iram_fast(const void *p) { + return ((intptr_t)p >= SOC_RTC_IRAM_LOW && (intptr_t)p < SOC_RTC_IRAM_HIGH); +} + +inline static bool IRAM_ATTR esp_ptr_in_rtc_dram_fast(const void *p) { + return ((intptr_t)p >= SOC_RTC_DRAM_LOW && (intptr_t)p < SOC_RTC_DRAM_HIGH); +} + +inline static bool IRAM_ATTR esp_ptr_in_rtc_slow(const void *p) { + return ((intptr_t)p >= SOC_RTC_DATA_LOW && (intptr_t)p < SOC_RTC_DATA_HIGH); +} + +/* Convert a D/IRAM DRAM pointer to equivalent word address in IRAM + + - Address must be word aligned + - Address must pass esp_ptr_in_diram_dram() test, or result will be invalid pointer +*/ +inline static void * IRAM_ATTR esp_ptr_diram_dram_to_iram(const void *p) { +#if SOC_DIRAM_INVERTED + return (void *) ( SOC_DIRAM_IRAM_LOW + (SOC_DIRAM_DRAM_HIGH - (intptr_t)p) - 4); +#else + return (void *) ( SOC_DIRAM_IRAM_LOW + ((intptr_t)p - SOC_DIRAM_DRAM_LOW) ); +#endif +} + +/* Convert a D/IRAM IRAM pointer to equivalent word address in DRAM + + - Address must be word aligned + - Address must pass esp_ptr_in_diram_iram() test, or result will be invalid pointer +*/ +inline static void * IRAM_ATTR esp_ptr_diram_iram_to_dram(const void *p) { +#if SOC_DIRAM_INVERTED + return (void *) ( SOC_DIRAM_DRAM_LOW + (SOC_DIRAM_IRAM_HIGH - (intptr_t)p) - 4); +#else + return (void *) ( SOC_DIRAM_DRAM_LOW + ((intptr_t)p - SOC_DIRAM_IRAM_LOW) ); +#endif +} inline static bool IRAM_ATTR esp_stack_ptr_is_sane(uint32_t sp) { //Check if stack ptr is in between SOC_DRAM_LOW and SOC_DRAM_HIGH, and 16 byte aligned. return !(sp < SOC_DRAM_LOW + 0x10 || sp > SOC_DRAM_HIGH - 0x10 || ((sp & 0xF) != 0)); } + diff --git a/components/soc/soc/esp32/include/soc/soc.h b/components/soc/soc/esp32/include/soc/soc.h index a4ac2ea08..b24633ace 100644 --- a/components/soc/soc/esp32/include/soc/soc.h +++ b/components/soc/soc/esp32/include/soc/soc.h @@ -277,6 +277,9 @@ #define SOC_MEM_INTERNAL_LOW 0x3FF90000 #define SOC_MEM_INTERNAL_HIGH 0x400C2000 +// Start (highest address) of ROM boot stack, only relevant during early boot +#define SOC_ROM_STACK_START 0x3ffe3f20 + //Interrupt hardware source table //This table is decided by hardware, don't touch this. #define ETS_WIFI_MAC_INTR_SOURCE 0/**< interrupt of WiFi MAC, level*/ diff --git a/components/soc/soc/esp32s2/include/soc/soc.h b/components/soc/soc/esp32s2/include/soc/soc.h index 1295c6c0c..ec1533045 100644 --- a/components/soc/soc/esp32s2/include/soc/soc.h +++ b/components/soc/soc/esp32s2/include/soc/soc.h @@ -283,6 +283,9 @@ #define SOC_MEM_INTERNAL_LOW 0x3FF9E000 #define SOC_MEM_INTERNAL_HIGH 0x40072000 +// Start (highest address) of ROM boot stack, only relevant during early boot +#define SOC_ROM_STACK_START 0x3fffe70c + //interrupt cpu using table, Please see the core-isa.h /************************************************************************************************************* * Intr num Level Type PRO CPU usage APP CPU uasge diff --git a/tools/ci/config/target-test.yml b/tools/ci/config/target-test.yml index 70c5bf81a..12fd31ec8 100644 --- a/tools/ci/config/target-test.yml +++ b/tools/ci/config/target-test.yml @@ -336,7 +336,7 @@ example_test_012: UT_001: extends: .unit_test_template - parallel: 34 + parallel: 36 tags: - ESP32_IDF - UT_T1_1