Browse Source

bootloader: Add fault injection resistance to Secure Boot bootloader verification

Goal is that multiple faults would be required to bypass a boot-time signature check.

- Also strengthens some address range checks for safe app memory addresses
- Change pre-enable logic to also check the bootloader signature before enabling SBV2 on ESP32

Add some additional checks for invalid sections:

- Sections only partially in DRAM or IRAM are invalid
- If a section is in D/IRAM, allow the possibility only some is in D/IRAM
- Only pass sections that are entirely in the same type of RTC memory region
Angus Gratton 6 năm trước cách đây
mục cha
commit
d40c69375c

+ 3 - 3
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)

+ 3 - 5
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

+ 141 - 0
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));
+}

+ 18 - 0
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);

+ 2 - 0
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 :

+ 2 - 1
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 :

+ 37 - 11
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
 

+ 1 - 1
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;
-}
+}

+ 2 - 2
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;

+ 22 - 11
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 <sys/param.h>
 #include <string.h>
@@ -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
+#endif

+ 4 - 3
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;

+ 191 - 49
components/bootloader_support/src/esp_image_format.c

@@ -16,6 +16,7 @@
 #include <soc/cpu.h>
 #include <bootloader_utility.h>
 #include <esp_secure_boot.h>
+#include <esp_fault.h>
 #include <esp_log.h>
 #include <esp_spi_flash.h>
 #include <bootloader_flash.h>
@@ -23,6 +24,7 @@
 #include <bootloader_sha.h>
 #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;
 }
 

+ 14 - 5
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();

+ 93 - 0
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

+ 56 - 22
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;
 };
 

+ 4 - 5
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;
 }

+ 74 - 0
components/heap/test/test_diram.c

@@ -0,0 +1,74 @@
+/*
+ Tests for D/IRAM support in heap capability allocator
+*/
+
+#include <esp_types.h>
+#include <stdio.h>
+#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);
+}

+ 38 - 0
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));
 }
+

+ 3 - 0
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*/

+ 3 - 0
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

+ 1 - 1
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