Bläddra i källkod

Merge branch 'feature/boot_time_optimisation' into 'master'

Optimise boot times, calculate SHA-256 hash of image during boot

See merge request !939

Angus Gratton 8 år sedan
förälder
incheckning
e468cdee1d

+ 15 - 6
components/app_update/esp_ota_ops.c

@@ -198,7 +198,6 @@ esp_err_t esp_ota_write(esp_ota_handle_t handle, const void *data, size_t size)
 esp_err_t esp_ota_end(esp_ota_handle_t handle)
 {
     ota_ops_entry_t *it;
-    size_t image_size;
     esp_err_t ret = ESP_OK;
 
     for (it = LIST_FIRST(&s_ota_ops_entries_head); it != NULL; it = LIST_NEXT(it, entries)) {
@@ -230,13 +229,19 @@ esp_err_t esp_ota_end(esp_ota_handle_t handle)
         it->partial_bytes = 0;
     }
 
-    if (esp_image_basic_verify(it->part->address, true, &image_size) != ESP_OK) {
+    esp_image_metadata_t data;
+    const esp_partition_pos_t part_pos = {
+      .offset = it->part->address,
+      .size = it->part->size,
+    };
+
+    if (esp_image_load(ESP_IMAGE_VERIFY, &part_pos, &data) != ESP_OK) {
         ret = ESP_ERR_OTA_VALIDATE_FAILED;
         goto cleanup;
     }
 
 #ifdef CONFIG_SECURE_BOOT_ENABLED
-    ret = esp_secure_boot_verify_signature(it->part->address, image_size);
+    ret = esp_secure_boot_verify_signature(it->part->address, data.image_len);
     if (ret != ESP_OK) {
         ret = ESP_ERR_OTA_VALIDATE_FAILED;
         goto cleanup;
@@ -365,18 +370,22 @@ static esp_err_t esp_rewrite_ota_data(esp_partition_subtype_t subtype)
 
 esp_err_t esp_ota_set_boot_partition(const esp_partition_t *partition)
 {
-    size_t image_size;
     const esp_partition_t *find_partition = NULL;
     if (partition == NULL) {
         return ESP_ERR_INVALID_ARG;
     }
 
-    if (esp_image_basic_verify(partition->address, true, &image_size) != ESP_OK) {
+    esp_image_metadata_t data;
+    const esp_partition_pos_t part_pos = {
+        .offset = partition->address,
+        .size = partition->size,
+    };
+    if (esp_image_load(ESP_IMAGE_VERIFY, &part_pos, &data) != ESP_OK) {
         return ESP_ERR_OTA_VALIDATE_FAILED;
     }
 
 #ifdef CONFIG_SECURE_BOOT_ENABLED
-    esp_err_t ret = esp_secure_boot_verify_signature(partition->address, image_size);
+    esp_err_t ret = esp_secure_boot_verify_signature(partition->address, data.image_len);
     if (ret != ESP_OK) {
         return ESP_ERR_OTA_VALIDATE_FAILED;
     }

+ 11 - 2
components/bootloader/Kconfig.projbuild

@@ -43,7 +43,16 @@ config BOOTLOADER_SPI_WP_PIN
 
         The default value (GPIO 7) is correct for WP pin on ESP32-D2WD integrated flash.
 
-endmenu  # Bootloader
+config BOOTLOADER_LTO
+       bool "Build bootloader with Link Time Optimisation"
+       default n
+       help
+           Setting this option enables gcc Link Time Optimisation for the bootloader build & link pass.
+
+           This gives a smaller bootloader binary (can be useful if secure boot & flash encryption & logging are all enabled), and can
+           give faster boot times, but it makes the bootloader harder to debug.
+
+endmenu  # Bootloader config
 
 
 menu "Security features"
@@ -217,7 +226,7 @@ config FLASH_ENCRYPTION_UART_BOOTLOADER_ALLOW_CACHE
 config SECURE_BOOT_TEST_MODE
        bool "Secure boot test mode: don't permanently set any efuses"
        depends on SECURE_BOOT_INSECURE
-       default N
+       default n
        help
           If this option is set, all permanent secure boot changes (via Efuse) are disabled.
 

+ 5 - 0
components/bootloader/subproject/main/Makefile.projbuild

@@ -2,3 +2,8 @@
 # paths can be added at this level (we need binary librtc to be
 # available to link bootloader).
 COMPONENT_SUBMODULES += $(IDF_PATH)/components/esp32/lib
+
+ifdef CONFIG_BOOTLOADER_LTO
+CFLAGS += -flto
+EXTRA_LDFLAGS += -Wl,-flto
+endif

+ 67 - 137
components/bootloader/subproject/main/bootloader_start.c

@@ -53,17 +53,18 @@
 
 extern int _bss_start;
 extern int _bss_end;
+extern int _data_start;
+extern int _data_end;
 
 static const char* TAG = "boot";
-/*
-We arrive here after the bootloader finished loading the program from flash. The hardware is mostly uninitialized,
-flash cache is down and the app CPU is in reset. We do have a stack, so we can do the initialization in C.
-*/
 
+/* Reduce literal size for some generic string literals */
+#define MAP_MSG "Mapping segment %d as %s"
+#define MAP_ERR_MSG "Image contains multiple %s segments. Only the last one will be mapped."
 
 void bootloader_main();
 static void unpack_load_app(const esp_partition_pos_t *app_node);
-void print_flash_info(const esp_image_header_t* pfhdr);
+static void print_flash_info(const esp_image_header_t* pfhdr);
 static void set_cache_and_start_app(uint32_t drom_addr,
     uint32_t drom_load_addr,
     uint32_t drom_size,
@@ -76,10 +77,26 @@ static void clock_configure(void);
 static void uart_console_configure(void);
 static void wdt_reset_check(void);
 
-void IRAM_ATTR call_start_cpu0()
+/*
+ * We arrive here after the ROM bootloader finished loading this second stage bootloader from flash.
+ * The hardware is mostly uninitialized, flash cache is down and the app CPU is in reset.
+ * We do have a stack, so we can do the initialization in C.
+ */
+void call_start_cpu0()
 {
     cpu_configure_region_protection();
 
+    /* Sanity check that static RAM is after the stack */
+#ifndef NDEBUG
+    {
+        int *sp = get_sp();
+        assert(&_bss_start <= &_bss_end);
+        assert(&_data_start <= &_data_end);
+        assert(sp < &_bss_start);
+        assert(sp < &_data_start);
+    }
+#endif
+
     //Clear bss
     memset(&_bss_start, 0, (&_bss_end - &_bss_start) * sizeof(_bss_start));
 
@@ -253,7 +270,7 @@ void bootloader_main()
     memset(&bs, 0, sizeof(bs));
 
     ESP_LOGI(TAG, "compile time " __TIME__ );
-    ets_set_appcpu_boot_addr(0); 
+    ets_set_appcpu_boot_addr(0);
 
     /* disable watch dog here */
     REG_CLR_BIT( RTC_CNTL_WDTCONFIG0_REG, RTC_CNTL_WDT_FLASHBOOT_MOD_EN );
@@ -276,7 +293,8 @@ void bootloader_main()
     bootloader_enable_qio_mode();
 #endif
 
-    if(esp_image_load_header(0x1000, true, &fhdr) != ESP_OK) {
+    if (bootloader_flash_read(ESP_BOOTLOADER_OFFSET, &fhdr,
+                              sizeof(esp_image_header_t), true) != ESP_OK) {
         ESP_LOGE(TAG, "failed to load bootloader header!");
         return;
     }
@@ -408,33 +426,16 @@ void bootloader_main()
 static void unpack_load_app(const esp_partition_pos_t* partition)
 {
     esp_err_t err;
-    esp_image_header_t image_header;
-    uint32_t image_length;
+    esp_image_metadata_t data;
 
-    /* TODO: verify the app image as part of OTA boot decision, so can have fallbacks */
-    err = esp_image_basic_verify(partition->offset, true, &image_length);
+    /* TODO: load the app image as part of OTA boot decision, so can fallback if loading fails */
+    /* Loading the image here also includes secure boot verification */
+    err = esp_image_load(ESP_IMAGE_LOAD, partition, &data);
     if (err != ESP_OK) {
         ESP_LOGE(TAG, "Failed to verify app image @ 0x%x (%d)", partition->offset, err);
         return;
     }
 
-#ifdef CONFIG_SECURE_BOOT_ENABLED
-    if (esp_secure_boot_enabled()) {
-        ESP_LOGI(TAG, "Verifying app signature @ 0x%x (length 0x%x)", partition->offset, image_length);
-        err = esp_secure_boot_verify_signature(partition->offset, image_length);
-        if (err != ESP_OK) {
-            ESP_LOGE(TAG, "App image @ 0x%x failed signature verification (%d)", partition->offset, err);
-            return;
-        }
-        ESP_LOGD(TAG, "App signature is valid");
-    }
-#endif
-
-    if (esp_image_load_header(partition->offset, true, &image_header) != ESP_OK) {
-        ESP_LOGE(TAG, "Failed to load app image header @ 0x%x", partition->offset);
-        return;
-    }
-
     uint32_t drom_addr = 0;
     uint32_t drom_load_addr = 0;
     uint32_t drom_size = 0;
@@ -442,124 +443,39 @@ static void unpack_load_app(const esp_partition_pos_t* partition)
     uint32_t irom_load_addr = 0;
     uint32_t irom_size = 0;
 
-    /* Reload the RTC memory segments whenever a non-deepsleep reset
-       is occurring */
-    bool load_rtc_memory = rtc_get_reset_reason(0) != DEEPSLEEP_RESET;
-
-    ESP_LOGD(TAG, "bin_header: %u %u %u %u %08x", image_header.magic,
-             image_header.segment_count,
-             image_header.spi_mode,
-             image_header.spi_size,
-             (unsigned)image_header.entry_addr);
-
-    /* Important: From here on this function cannot access any global data (bss/data segments),
-       as loading the app image may overwrite these.
-    */
-    for (int segment = 0; segment < image_header.segment_count; segment++) {
-        esp_image_segment_header_t segment_header;
-        uint32_t data_offs;
-        if(esp_image_load_segment_header(segment, partition->offset,
-                                         &image_header, true,
-                                         &segment_header, &data_offs) != ESP_OK) {
-            ESP_LOGE(TAG, "failed to load segment header #%d", segment);
-            return;
-        }
-
-        const uint32_t address = segment_header.load_addr;
-        bool load = true;
-        bool map = false;
-        if (address == 0x00000000) {        // padding, ignore block
-            load = false;
-        }
-        if (address == 0x00000004) {
-            load = false;                   // md5 checksum block
-            // TODO: actually check md5
-        }
-
-        if (address >= SOC_DROM_LOW && address < SOC_DROM_HIGH) {
-            ESP_LOGD(TAG, "found drom segment, map from %08x to %08x", data_offs,
-                      segment_header.load_addr);
-            drom_addr = data_offs;
-            drom_load_addr = segment_header.load_addr;
-            drom_size = segment_header.data_len + sizeof(segment_header);
-            load = false;
-            map = true;
-        }
-
-        if (address >= SOC_IROM_LOW && address < SOC_IROM_HIGH) {
-            ESP_LOGD(TAG, "found irom segment, map from %08x to %08x", data_offs,
-                      segment_header.load_addr);
-            irom_addr = data_offs;
-            irom_load_addr = segment_header.load_addr;
-            irom_size = segment_header.data_len + sizeof(segment_header);
-            load = false;
-            map = true;
-        }
-
-        if (!load_rtc_memory && address >= SOC_RTC_IRAM_LOW && address < SOC_RTC_IRAM_HIGH) {
-            ESP_LOGD(TAG, "Skipping RTC code segment at %08x\n", data_offs);
-            load = false;
-        }
-
-        if (!load_rtc_memory && address >= SOC_RTC_DATA_LOW && address < SOC_RTC_DATA_HIGH) {
-            ESP_LOGD(TAG, "Skipping RTC data segment at %08x\n", data_offs);
-            load = false;
-        }
-
-        ESP_LOGI(TAG, "segment %d: paddr=0x%08x vaddr=0x%08x size=0x%05x (%6d) %s", segment, data_offs - sizeof(esp_image_segment_header_t),
-                 segment_header.load_addr, segment_header.data_len, segment_header.data_len, (load)?"load":(map)?"map":"");
-
-        if (load) {
-            intptr_t sp, start_addr, end_addr;
-            ESP_LOGV(TAG, "bootloader_mmap data_offs=%08x data_len=%08x", data_offs, segment_header.data_len);
-
-            start_addr = segment_header.load_addr;
-            end_addr = start_addr + segment_header.data_len;
-
-            /* Before loading segment, check it doesn't clobber
-               bootloader RAM... */
-
-            if (end_addr < 0x40000000) {
-                if (end_addr > 0x3FFE0000) {
-                    /* Temporary workaround for an ugly crash, until we allow >192KB of static DRAM */
-                    ESP_LOGE(TAG, "DRAM segment %d (start 0x%08x end 0x%08x) too large for IDF to boot",
-                             segment, start_addr, end_addr);
-                    return;
-                }
-
-                sp = (intptr_t)get_sp();
-                if (end_addr > sp) {
-                    ESP_LOGE(TAG, "Segment %d end address %08x overlaps bootloader stack %08x - can't load",
-                         segment, end_addr, sp);
-                    return;
-                }
-                if (end_addr > sp - 256) {
-                    /* We don't know for sure this is the stack high water mark, so warn if
-                       it seems like we may overflow.
-                    */
-                    ESP_LOGW(TAG, "Segment %d end address %08x close to stack pointer %08x",
-                             segment, end_addr, sp);
-                }
+    // Find DROM & IROM addresses, to configure cache mappings
+    for (int i = 0; i < data.image.segment_count; i++) {
+        esp_image_segment_header_t *header = &data.segments[i];
+        if (header->load_addr >= SOC_IROM_LOW && header->load_addr < SOC_IROM_HIGH) {
+            if (drom_addr != 0) {
+                ESP_LOGE(TAG, MAP_ERR_MSG, "DROM");
+            } else {
+                ESP_LOGD(TAG, "Mapping segment %d as %s", i, "DROM");
             }
-
-            const void *data = bootloader_mmap(data_offs, segment_header.data_len);
-            if(!data) {
-                ESP_LOGE(TAG, "bootloader_mmap(0x%xc, 0x%x) failed",
-                         data_offs, segment_header.data_len);
-                return;
+            drom_addr = data.segment_data[i];
+            drom_load_addr = header->load_addr;
+            drom_size = header->data_len;
+        }
+        if (header->load_addr >= SOC_DROM_LOW && header->load_addr < SOC_DROM_HIGH) {
+            if (irom_addr != 0) {
+                ESP_LOGE(TAG, MAP_ERR_MSG, "IROM");
+            } else {
+                ESP_LOGD(TAG, "Mapping segment %d as %s", i, "IROM");
             }
-            memcpy((void *)segment_header.load_addr, data, segment_header.data_len);
-            bootloader_munmap(data);
+            irom_addr = data.segment_data[i];
+            irom_load_addr = header->load_addr;
+            irom_size = header->data_len;
         }
     }
 
+    ESP_LOGD(TAG, "calling set_cache_and_start_app");
     set_cache_and_start_app(drom_addr,
         drom_load_addr,
         drom_size,
         irom_addr,
         irom_load_addr,
         irom_size,
-        image_header.entry_addr);
+        data.image.entry_addr);
 }
 
 static void set_cache_and_start_app(
@@ -574,6 +490,14 @@ static void set_cache_and_start_app(
     ESP_LOGD(TAG, "configure drom and irom and start");
     Cache_Read_Disable( 0 );
     Cache_Flush( 0 );
+
+    /* Clear the MMU entries that are already set up,
+       so the new app only has the mappings it creates.
+    */
+    for (int i = 0; i < DPORT_FLASH_MMU_TABLE_SIZE; i++) {
+        DPORT_PRO_FLASH_MMU_TABLE[i] = DPORT_FLASH_MMU_TABLE_INVALID_VAL;
+    }
+
     uint32_t drom_page_count = (drom_size + 64*1024 - 1) / (64*1024); // round up to 64k
     ESP_LOGV(TAG, "d mmu set paddr=%08x vaddr=%08x size=%d n=%d", drom_addr & 0xffff0000, drom_load_addr & 0xffff0000, drom_size, drom_page_count );
     int rc = cache_flash_mmu_set( 0, 0, drom_load_addr & 0xffff0000, drom_addr & 0xffff0000, 64, drom_page_count );
@@ -632,7 +556,7 @@ static void update_flash_config(const esp_image_header_t* pfhdr)
     Cache_Read_Enable( 0 );
 }
 
-void print_flash_info(const esp_image_header_t* phdr)
+static void print_flash_info(const esp_image_header_t* phdr)
 {
 #if (BOOT_LOG_LEVEL >= BOOT_LOG_LEVEL_NOTICE)
 
@@ -862,3 +786,9 @@ static void wdt_reset_check(void)
     }
     wdt_reset_cpu0_info_enable();
 }
+
+void __assert_func(const char *file, int line, const char *func, const char *expr)
+{
+    ESP_LOGE(TAG, "Assert failed in %s, %s:%d (%s)", func, file, line, expr);
+    while(1) {}
+}

+ 26 - 42
components/bootloader/subproject/main/esp32.bootloader.ld

@@ -1,23 +1,21 @@
 /*
 Linker file used to link the bootloader.
-
-*WARNING* For now this linker dumps everything into IRAM/DRAM. ToDo: move
-some/most stuff to DROM/IROM.
-
 */
 
 
-/* THESE ARE THE VIRTUAL RUNTIME ADDRESSES  */
-/* The load addresses are defined later using the AT statements. */
+/* Simplified memory map for the bootloader
+
+   The main purpose is to make sure the bootloader can load into main memory
+   without overwriting itself.
+*/
 MEMORY
 {
-  /* All these values assume the flash cache is on, and have the blocks this uses subtracted from the length
-  of the various regions. The 'data access port' dram/drom regions map to the same iram/irom regions but
-  are connected to the data port of the CPU and eg allow bytewise access. */
-  dport0_seg (RW) :                 	org = 0x3FF00000, len = 0x10		/* IO */
-  iram_seg (RWX) :                 	org = 0x40080000, len = 0x400		/* 1k of IRAM used by bootloader functions which need to flush/enable APP CPU cache */ 
-  iram_pool_1_seg (RWX) :           org = 0x40078000, len = 0x8000    /* IRAM POOL1, used for APP CPU cache. We can abuse it in bootloader because APP CPU is still held in reset, until we enable APP CPU cache */
-  dram_seg (RW) :                  	org = 0x3FFF0000, len = 0x10000		/* 64k at the end of DRAM, after ROM bootloader stack */
+  /* I/O */
+  dport0_seg (RW) :                 	org = 0x3FF00000, len = 0x10
+  /* IRAM POOL1, used for APP CPU cache. We can abuse it in bootloader because APP CPU is still held in reset, the main app enables APP CPU cache */
+  iram_seg (RWX) :           org = 0x40078000, len = 0x8000
+  /* 64k at the end of DRAM, after ROM bootloader stack */
+  dram_seg (RW) :                  	org = 0x3FFF0000, len = 0x10000
 }
 
 /*  Default entry point:  */
@@ -28,19 +26,10 @@ SECTIONS
 {
   .iram1.text :
   {
-    _init_start = ABSOLUTE(.);
-    *(.UserEnter.literal);
-    *(.UserEnter.text);
     . = ALIGN (16);
     *(.entry.text)
     *(.init.literal)
     *(.init)
-    _init_end = ABSOLUTE(.);
-
-	  /* Code marked as runnning out of IRAM */
-	  _iram_text_start = ABSOLUTE(.);
-    *(.iram1 .iram1.*)
-	  _iram_text_end = ABSOLUTE(.);
   } > iram_seg
 
 
@@ -58,7 +47,7 @@ SECTIONS
     *(.sbss2.*)
     *(.gnu.linkonce.sb2.*)
     *(.dynbss)
-    KEEP(*(.bss))
+    *(.bss)
     *(.bss.*)
     *(.gnu.linkonce.b.*)
     *(COMMON)
@@ -66,33 +55,28 @@ SECTIONS
     _bss_end = ABSOLUTE(.);
   } >dram_seg
 
-
   .dram0.data :
   {
     _data_start = ABSOLUTE(.);
-    KEEP(*(.data))
-    KEEP(*(.data.*))
-    KEEP(*(.gnu.linkonce.d.*))
-    KEEP(*(.data1))
-    KEEP(*(.sdata))
-    KEEP(*(.sdata.*))
-    KEEP(*(.gnu.linkonce.s.*))
-    KEEP(*(.sdata2))
-    KEEP(*(.sdata2.*))
-    KEEP(*(.gnu.linkonce.s2.*))
-    KEEP(*(.jcr))
+    *(.data)
+    *(.data.*)
+    *(.gnu.linkonce.d.*)
+    *(.data1)
+    *(.sdata)
+    *(.sdata.*)
+    *(.gnu.linkonce.s.*)
+    *(.sdata2)
+    *(.sdata2.*)
+    *(.gnu.linkonce.s2.*)
+    *(.jcr)
     _data_end = ABSOLUTE(.);
   } >dram_seg
 
-
-
-
   .dram0.rodata :
   {
     _rodata_start = ABSOLUTE(.);
     *(.rodata)
     *(.rodata.*)
-	*(.irom1.text) /* catch stray ICACHE_RODATA_ATTR */
     *(.gnu.linkonce.r.*)
     *(.rodata1)
     __XT_EXCEPTION_TABLE_ = ABSOLUTE(.);
@@ -132,17 +116,17 @@ SECTIONS
     _heap_start = ABSOLUTE(.);
   } >dram_seg
 
-  .iram_pool_1.text :
+  .iram.text :
   {
     _stext = .;
     _text_start = ABSOLUTE(.);
     *(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)
-	*(.irom0.text) /* catch stray ICACHE_RODATA_ATTR */
+    *(.iram1 .iram1.*) /* catch stray IRAM_ATTR */
     *(.fini.literal)
     *(.fini)
     *(.gnu.version)
     _text_end = ABSOLUTE(.);
     _etext = .;
-  } >iram_pool_1_seg
+  } > iram_seg
 
 }

+ 63 - 53
components/bootloader_support/include/esp_image_format.h

@@ -11,11 +11,11 @@
 // 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.
-#ifndef __ESP32_IMAGE_FORMAT_H
-#define __ESP32_IMAGE_FORMAT_H
+#pragma once
 
 #include <stdbool.h>
 #include <esp_err.h>
+#include "esp_flash_partitions.h"
 
 #define ESP_ERR_IMAGE_BASE       0x2000
 #define ESP_ERR_IMAGE_FLASH_FAIL (ESP_ERR_IMAGE_BASE + 1)
@@ -59,13 +59,27 @@ typedef enum {
 typedef struct {
     uint8_t magic;
     uint8_t segment_count;
-    uint8_t spi_mode;      /* flash read mode (esp_image_spi_mode_t as uint8_t) */
-    uint8_t spi_speed: 4;  /* flash frequency (esp_image_spi_freq_t as uint8_t) */
-    uint8_t spi_size: 4;   /* flash chip size (esp_image_flash_size_t as uint8_t) */
+    /* flash read mode (esp_image_spi_mode_t as uint8_t) */
+    uint8_t spi_mode;
+    /* flash frequency (esp_image_spi_freq_t as uint8_t) */
+    uint8_t spi_speed: 4;
+    /* flash chip size (esp_image_flash_size_t as uint8_t) */
+    uint8_t spi_size: 4;
     uint32_t entry_addr;
-    uint8_t encrypt_flag;    /* encrypt flag */
-    uint8_t extra_header[15]; /* ESP32 additional header, unused by second bootloader */
-}  esp_image_header_t;
+    /* WP pin when SPI pins set via efuse (read by ROM bootloader, the IDF bootloader uses software to configure the WP
+     * pin and sets this field to 0xEE=disabled) */
+    uint8_t wp_pin;
+    /* Drive settings for the SPI flash pins (read by ROM bootloader) */
+    uint8_t spi_pin_drv[3];
+    /* Reserved bytes in ESP32 additional header space, currently unused */
+    uint8_t reserved[11];
+    /* If 1, a SHA256 digest "simple hash" (of the entire image) is appended after the checksum. Included in image length. This digest
+     * is separate to secure boot and only used for detecting corruption. For secure boot signed images, the signature
+     * is appended after this (and the simple hash is included in the signed data). */
+    uint8_t hash_appended;
+} __attribute__((packed))  esp_image_header_t;
+
+_Static_assert(sizeof(esp_image_header_t) == 24, "binary image header should be 24 bytes");
 
 /* Header of binary image segment */
 typedef struct {
@@ -73,62 +87,60 @@ typedef struct {
     uint32_t data_len;
 } esp_image_segment_header_t;
 
+#define ESP_IMAGE_MAX_SEGMENTS 16
 
-/**
- * @brief Read an ESP image header from flash.
- *
- * If encryption is enabled, data will be transparently decrypted.
- *
- * @param src_addr Address in flash to load image header. Must be 4 byte aligned.
- * @param log_errors Log error output if image header appears invalid.
- * @param[out] image_header Pointer to an esp_image_header_t struture to be filled with data. If the function fails, contents are undefined.
- *
- * @return ESP_OK if image header was loaded, ESP_ERR_IMAGE_FLASH_FAIL
- * if a SPI flash error occurs, ESP_ERR_IMAGE_INVALID if the image header
- * appears invalid.
- */
-esp_err_t esp_image_load_header(uint32_t src_addr, bool log_errors, esp_image_header_t *image_header);
+/* Structure to hold on-flash image metadata */
+typedef struct {
+  uint32_t start_addr;   /* Start address of image */
+  esp_image_header_t image; /* Header for entire image */
+  esp_image_segment_header_t segments[ESP_IMAGE_MAX_SEGMENTS]; /* Per-segment header data */
+  uint32_t segment_data[ESP_IMAGE_MAX_SEGMENTS]; /* Data offsets for each segment */
+  uint32_t image_len; /* Length of image on flash, in bytes */
+} esp_image_metadata_t;
+
+/* Mode selection for esp_image_load() */
+typedef enum {
+    ESP_IMAGE_VERIFY,        /* Verify image contents, load metadata. Print errorsors. */
+    ESP_IMAGE_VERIFY_SILENT, /* Verify image contents, load metadata. Don't print errors. */
+#ifdef BOOTLOADER_BUILD
+    ESP_IMAGE_LOAD,          /* Verify image contents, load to memory. Print errors. */
+#endif
+} esp_image_load_mode_t;
 
 /**
- * @brief Read the segment header and data offset of a segment in the image.
+ * @brief Verify and (optionally, in bootloader mode) load an app image.
  *
  * If encryption is enabled, data will be transparently decrypted.
  *
- * @param index Index of the segment to load information for.
- * @param src_addr Base address in flash of the image.
- * @param[in] image_header Pointer to the flash image header, already loaded by @ref esp_image_load_header().
- * @param log_errors Log errors reading the segment header.
- * @param[out] segment_header Pointer to a segment header structure to be filled with data. If the function fails, contents are undefined.
- * @param[out] segment_data_offset Pointer to the data offset of the segment.
+ * @param mode Mode of operation (verify, silent verify, or load).
+ * @param part Partition to load the app from.
+ * @param[inout] data Pointer to the image metadata structure which is be filled in by this function. 'start_addr' member should be set (to the start address of the image.) Other fields will all be initialised by this function.
  *
- * @return ESP_OK if segment_header & segment_data_offset were loaded successfully, ESP_ERR_IMAGE_FLASH_FAIL if a SPI flash error occurs, ESP_ERR_IMAGE_INVALID if the image header appears invalid, ESP_ERR_INVALID_ARG if the index is invalid.
+ * Image validation checks:
+ * - Magic byte.
+ * - Partition smaller than 16MB.
+ * - All segments & image fit in partition.
+ * - 8 bit image checksum is valid.
+ * - SHA-256 of image is valid (if image has this appended).
+ * - (Signature) if signature verification is enabled.
+ *
+ * @return
+ * - ESP_OK if verify or load was successful
+ * - ESP_ERR_IMAGE_FLASH_FAIL if a SPI flash error occurs
+ * - ESP_ERR_IMAGE_INVALID if the image appears invalid.
+ * - ESP_ERR_INVALID_ARG if the partition or data pointers are invalid.
  */
-esp_err_t esp_image_load_segment_header(uint8_t index, uint32_t src_addr, const esp_image_header_t *image_header, bool log_errors, esp_image_segment_header_t *segment_header, uint32_t *segment_data_offset);
-
+esp_err_t esp_image_load(esp_image_load_mode_t mode, const esp_partition_pos_t *part, esp_image_metadata_t *data);
 
 /**
- * @brief Non-cryptographically validate app image integrity. On success, length of image is provided to caller.
- *
- * If the image has a secure boot signature appended, the signature is not checked and this length is not included in the
- * output value.
- *
- * Image validation checks:
- * - Magic byte
- * - No single segment longer than 16MB
- * - Total image no longer than 16MB
- * - 8 bit image checksum is valid
+ * @brief Verify the bootloader image.
  *
- * If flash encryption is enabled, the image will be tranpsarently decrypted.
- *
- * @param src_addr Offset of the start of the image in flash. Must be 4 byte aligned.
- * @param allow_decrypt If true and flash encryption is enabled, the image will be transparently decrypted.
- * @param log_errors Log errors verifying the image.
- * @param[out] length Length of the image, set to a value if the image is valid. Can be null.
- *
- * @return ESP_OK if image is valid, ESP_FAIL or ESP_ERR_IMAGE_INVALID on errors.
+ * @param[out] If result is ESP_OK and this pointer is non-NULL, it
+ * will be set to the length of the bootloader image.
  *
+ * @return As per esp_image_load_metadata().
  */
-esp_err_t esp_image_basic_verify(uint32_t src_addr, bool log_errors, uint32_t *length);
+esp_err_t esp_image_verify_bootloader(uint32_t *length);
 
 
 typedef struct {
@@ -139,5 +151,3 @@ typedef struct {
     uint32_t irom_load_addr;
     uint32_t irom_size;
 } esp_image_flash_mapping_t;
-
-#endif

+ 18 - 2
components/bootloader_support/include/esp_secure_boot.h

@@ -11,13 +11,16 @@
 // 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.
-#ifndef __ESP32_SECUREBOOT_H
-#define __ESP32_SECUREBOOT_H
+#pragma once
 
 #include <stdbool.h>
 #include <esp_err.h>
 #include "soc/efuse_reg.h"
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 /* Support functions for secure boot features.
 
    Can be compiled as part of app or bootloader code.
@@ -74,12 +77,22 @@ esp_err_t esp_secure_boot_permanently_enable(void);
  */
 esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length);
 
+/** @brief Verify the secure boot signature block (deterministic ECDSA w/ SHA256) based on the SHA256 hash of some data.
+ *
+ * Similar to esp_secure_boot_verify_signature(), but can be used when the digest is precalculated.
+ * @param sig_block Pointer to signature block data
+ * @param image_digest Pointer to 32 byte buffer holding SHA-256 hash.
+ *
+ */
+
 /** @brief Secure boot verification block, on-flash data format. */
 typedef struct {
     uint32_t version;
     uint8_t signature[64];
 } esp_secure_boot_sig_block_t;
 
+esp_err_t esp_secure_boot_verify_signature_block(const esp_secure_boot_sig_block_t *sig_block, const uint8_t *image_digest);
+
 #define FLASH_OFFS_SECURE_BOOT_IV_DIGEST 0
 
 /** @brief Secure boot IV+digest header */
@@ -88,4 +101,7 @@ typedef struct {
     uint8_t digest[64];
 } esp_secure_boot_iv_digest_t;
 
+
+#ifdef __cplusplus
+}
 #endif

+ 32 - 0
components/bootloader_support/include_priv/bootloader_sha.h

@@ -0,0 +1,32 @@
+// Copyright 2017 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.
+#pragma once
+
+/* Provide a SHA256 API for bootloader_support code,
+   that can be used from bootloader or app code.
+
+   This header is available to source code in the bootloader & bootloader_support components only.
+   Use mbedTLS APIs or include hwcrypto/sha.h to calculate SHA256 in IDF apps.
+*/
+
+#include <stdint.h>
+#include <stdlib.h>
+
+typedef void *bootloader_sha256_handle_t;
+
+bootloader_sha256_handle_t bootloader_sha256_start();
+
+void bootloader_sha256_data(bootloader_sha256_handle_t handle, const void *data, size_t data_len);
+
+void bootloader_sha256_finish(bootloader_sha256_handle_t handle, uint8_t *digest);

+ 5 - 2
components/bootloader_support/src/bootloader_flash.c

@@ -32,11 +32,13 @@ const void *bootloader_mmap(uint32_t src_addr, uint32_t size)
         return NULL; /* existing mapping in use... */
     }
     const void *result = NULL;
-    esp_err_t err = spi_flash_mmap(src_addr, size, SPI_FLASH_MMAP_DATA, &result, &map);
+    uint32_t src_page = src_addr & ~(SPI_FLASH_MMU_PAGE_SIZE-1);
+    size += (src_addr - src_page);
+    esp_err_t err = spi_flash_mmap(src_page, size, SPI_FLASH_MMAP_DATA, &result, &map);
     if (err != ESP_OK) {
         result = NULL;
     }
-    return result;
+    return (void *)((intptr_t)result + (src_addr - src_page));
 }
 
 void bootloader_munmap(const void *mapping)
@@ -88,6 +90,7 @@ static const char *TAG = "bootloader_flash";
 
 static bool mapped;
 
+// Current bootloader mapping (ab)used for bootloader_read()
 static uint32_t current_read_mapping = UINT32_MAX;
 
 const void *bootloader_mmap(uint32_t src_addr, uint32_t size)

+ 166 - 0
components/bootloader_support/src/bootloader_sha.c

@@ -0,0 +1,166 @@
+// Copyright 2017 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 "bootloader_sha.h"
+#include <stdbool.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/param.h>
+
+#ifndef BOOTLOADER_BUILD
+// App version is a wrapper around mbedTLS SHA API
+#include <mbedtls/sha256.h>
+
+bootloader_sha256_handle_t bootloader_sha256_start()
+{
+    mbedtls_sha256_context *ctx = (mbedtls_sha256_context *)malloc(sizeof(mbedtls_sha256_context));
+    if (!ctx) {
+        return NULL;
+    }
+    mbedtls_sha256_init(ctx);
+    mbedtls_sha256_starts(ctx, false);
+    return ctx;
+}
+
+void bootloader_sha256_data(bootloader_sha256_handle_t handle, const void *data, size_t data_len)
+{
+    assert(handle != NULL);
+    mbedtls_sha256_context *ctx = (mbedtls_sha256_context *)handle;
+    mbedtls_sha256_update(ctx, data, data_len);
+}
+
+void bootloader_sha256_finish(bootloader_sha256_handle_t handle, uint8_t *digest)
+{
+    assert(handle != NULL);
+    mbedtls_sha256_context *ctx = (mbedtls_sha256_context *)handle;
+    if (digest != NULL) {
+        mbedtls_sha256_finish(ctx, digest);
+    }
+    mbedtls_sha256_free(ctx);
+    free(handle);
+}
+
+#else // Bootloader version
+
+#include "rom/sha.h"
+#include "soc/dport_reg.h"
+#include "soc/hwcrypto_reg.h"
+
+#include "rom/ets_sys.h" // TO REMOVE
+
+static uint32_t words_hashed;
+
+// Words per SHA256 block
+static const size_t BLOCK_WORDS = (64/sizeof(uint32_t));
+// Words in final SHA256 digest
+static const size_t DIGEST_WORDS = (32/sizeof(uint32_t));
+
+bootloader_sha256_handle_t bootloader_sha256_start()
+{
+    // Enable SHA hardware
+    ets_sha_enable();
+    words_hashed = 0;
+    return (bootloader_sha256_handle_t)&words_hashed; // Meaningless non-NULL value
+}
+
+void bootloader_sha256_data(bootloader_sha256_handle_t handle, const void *data, size_t data_len)
+{
+    assert(handle != NULL);
+    assert(data_len % 4 == 0);
+
+    const uint32_t *w = (const uint32_t *)data;
+    size_t word_len = data_len / 4;
+    uint32_t *sha_text_reg = (uint32_t *)(SHA_TEXT_BASE);
+
+    //ets_printf("word_len %d so far %d\n", word_len, words_hashed);
+    while (word_len > 0) {
+        size_t block_count = words_hashed % BLOCK_WORDS;
+        size_t copy_words = (BLOCK_WORDS - block_count);
+
+        copy_words = MIN(word_len, copy_words);
+
+        // Wait for SHA engine idle
+        while(REG_READ(SHA_256_BUSY_REG) != 0) { }
+
+        // Copy to memory block
+        //ets_printf("block_count %d copy_words %d\n", block_count, copy_words);
+        for (int i = 0; i < copy_words; i++) {
+            sha_text_reg[block_count + i] = __builtin_bswap32(w[i]);
+        }
+        asm volatile ("memw");
+
+        // Update counters
+        words_hashed += copy_words;
+        block_count += copy_words;
+        word_len -= copy_words;
+        w += copy_words;
+
+        // If we loaded a full block, run the SHA engine
+        if (block_count == BLOCK_WORDS) {
+            //ets_printf("running engine @ count %d\n", words_hashed);
+            if (words_hashed == BLOCK_WORDS) {
+                REG_WRITE(SHA_256_START_REG, 1);
+            } else {
+                REG_WRITE(SHA_256_CONTINUE_REG, 1);
+            }
+            block_count = 0;
+        }
+    }
+}
+
+void bootloader_sha256_finish(bootloader_sha256_handle_t handle, uint8_t *digest)
+{
+    assert(handle != NULL);
+
+    if (digest == NULL) {
+        return; // We'd free resources here, but there are none to free
+    }
+
+    uint32_t data_words = words_hashed;
+
+    // Pad to a 55 byte long block loaded in the engine
+    // (leaving 1 byte 0x80 plus variable padding plus 8 bytes of length,
+    // to fill a 64 byte block.)
+    int block_bytes = (words_hashed % BLOCK_WORDS) * 4;
+    int pad_bytes = 55 - block_bytes;
+    if (pad_bytes < 0) {
+        pad_bytes += 64;
+    }
+    static const uint8_t padding[64] = { 0x80, 0, };
+
+    pad_bytes += 5; // 1 byte for 0x80 plus first 4 bytes of the 64-bit length
+    assert(pad_bytes % 4 == 0); // should be, as (block_bytes % 4 == 0)
+
+    bootloader_sha256_data(handle, padding, pad_bytes);
+
+    assert(words_hashed % BLOCK_WORDS == 60/4); // 32-bits left in block
+
+    // Calculate 32-bit length for final 32 bits of data
+    uint32_t bit_count = __builtin_bswap32( data_words * 32 );
+    bootloader_sha256_data(handle, &bit_count, sizeof(bit_count));
+
+    assert(words_hashed % BLOCK_WORDS == 0);
+
+    while(REG_READ(SHA_256_BUSY_REG) == 1) { }
+    REG_WRITE(SHA_256_LOAD_REG, 1);
+    while(REG_READ(SHA_256_BUSY_REG) == 1) { }
+
+    uint32_t *digest_words = (uint32_t *)digest;
+    uint32_t *sha_text_reg = (uint32_t *)(SHA_TEXT_BASE);
+    for (int i = 0; i < DIGEST_WORDS; i++) {
+        digest_words[i] = __builtin_bswap32(sha_text_reg[i]);
+    }
+    asm volatile ("memw");
+}
+
+#endif

+ 448 - 106
components/bootloader_support/src/esp_image_format.c

@@ -12,178 +12,520 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 #include <string.h>
+#include <sys/param.h>
 
+#include <rom/rtc.h>
+#include <soc/cpu.h>
 #include <esp_image_format.h>
+#include <esp_secure_boot.h>
 #include <esp_log.h>
 #include <bootloader_flash.h>
+#include <bootloader_random.h>
+#include <bootloader_sha.h>
 
 static const char *TAG = "esp_image";
 
+#define HASH_LEN 32 /* SHA-256 digest length */
+
 #define SIXTEEN_MB 0x1000000
 #define ESP_ROM_CHECKSUM_INITIAL 0xEF
 
-esp_err_t esp_image_load_header(uint32_t src_addr, bool log_errors, esp_image_header_t *image_header)
+/* Headroom to ensure between stack SP (at time of checking) and data loaded from flash */
+#define STACK_LOAD_HEADROOM 32768
+
+#ifdef BOOTLOADER_BUILD
+/* 64 bits of random data to obfuscate loaded RAM with, until verification is complete
+   (Means loaded code isn't executable until after the secure boot check.)
+*/
+static uint32_t ram_obfs_value[2];
+#endif
+
+/* Return true if load_addr is an address the bootloader should load into */
+static bool should_load(uint32_t load_addr);
+/* Return true if load_addr is an address the bootloader should map via flash cache */
+static bool should_map(uint32_t load_addr);
+
+/* Load or verify a segment */
+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);
+
+/* Verify the main image header */
+static esp_err_t verify_image_header(uint32_t src_addr, const esp_image_header_t *image, bool silent);
+
+/* Verify a segment header */
+static esp_err_t verify_segment_header(int index, const esp_image_segment_header_t *segment, uint32_t segment_data_offs, bool silent);
+
+/* Log-and-fail macro for use in esp_image_load */
+#define FAIL_LOAD(...) do {                         \
+        if (!silent) {                              \
+            ESP_LOGE(TAG, __VA_ARGS__);             \
+        }                                           \
+        goto err;                                   \
+    }                                               \
+    while(0)
+
+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(bootloader_sha256_handle_t sha_handle, esp_image_metadata_t *data);
+static esp_err_t __attribute__((unused)) verify_simple_hash(bootloader_sha256_handle_t sha_handle, esp_image_metadata_t *data);
+
+esp_err_t esp_image_load(esp_image_load_mode_t mode, const esp_partition_pos_t *part, esp_image_metadata_t *data)
 {
-    esp_err_t err;
-    ESP_LOGD(TAG, "reading image header @ 0x%x", src_addr);
+#ifdef BOOTLOADER_BUILD
+    bool do_load = (mode == ESP_IMAGE_LOAD);
+#else
+    bool do_load = false; // Can't load the image in app mode
+#endif
+    bool silent = (mode == ESP_IMAGE_VERIFY_SILENT);
+    esp_err_t err = ESP_OK;
+    // checksum the image a word at a time. This shaves 30-40ms per MB of image size
+    uint32_t checksum_word = ESP_ROM_CHECKSUM_INITIAL;
+    bootloader_sha256_handle_t sha_handle = NULL;
 
-    err = bootloader_flash_read(src_addr, image_header, sizeof(esp_image_header_t), true);
+    if (data == NULL || part == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
 
-    if (err == ESP_OK) {
-        if (image_header->magic != ESP_IMAGE_HEADER_MAGIC) {
-            if (log_errors) {
-                ESP_LOGE(TAG, "image at 0x%x has invalid magic byte", src_addr);
-            }
-            err = ESP_ERR_IMAGE_INVALID;
+    if (part->size > SIXTEEN_MB) {
+        err = ESP_ERR_INVALID_ARG;
+        FAIL_LOAD("partition size %d invalid, larger than 16MB", part->size);
+    }
+
+    bzero(data, sizeof(esp_image_metadata_t));
+    data->start_addr = part->offset;
+
+    ESP_LOGD(TAG, "reading image header @ 0x%x", data->start_addr);
+    err = bootloader_flash_read(data->start_addr, &data->image, sizeof(esp_image_header_t), true);
+    if (err != ESP_OK) {
+        goto err;
+    }
+
+    // Calculate SHA-256 of image if secure boot is on, or if image has a hash appended
+#ifdef CONFIG_SECURE_BOOT_ENABLED
+    if (1) {
+#else
+    if (data->image.hash_appended) {
+#endif
+        sha_handle = bootloader_sha256_start();
+        if (sha_handle == NULL) {
+            return ESP_ERR_NO_MEM;
         }
-        if (log_errors) {
-            if (image_header->spi_mode > ESP_IMAGE_SPI_MODE_SLOW_READ) {
-                ESP_LOGW(TAG, "image at 0x%x has invalid SPI mode %d", src_addr, image_header->spi_mode);
-            }
-            if (image_header->spi_speed > ESP_IMAGE_SPI_SPEED_80M) {
-                ESP_LOGW(TAG, "image at 0x%x has invalid SPI speed %d", src_addr, image_header->spi_speed);
-            }
-            if (image_header->spi_size > ESP_IMAGE_FLASH_SIZE_MAX) {
-                ESP_LOGW(TAG, "image at 0x%x has invalid SPI size %d", src_addr, image_header->spi_size);
-            }
+        bootloader_sha256_data(sha_handle, &data->image, sizeof(esp_image_header_t));
+    }
+
+    ESP_LOGD(TAG, "image header: 0x%02x 0x%02x 0x%02x 0x%02x %08x",
+             data->image.magic,
+             data->image.segment_count,
+             data->image.spi_mode,
+             data->image.spi_size,
+             data->image.entry_addr);
+
+    err = verify_image_header(data->start_addr, &data->image, silent);
+    if (err != ESP_OK) {
+goto err;
+    }
+
+    if (data->image.segment_count > ESP_IMAGE_MAX_SEGMENTS) {
+        FAIL_LOAD("image at 0x%x segment count %d exceeds max %d",
+                  data->start_addr, data->image.segment_count, ESP_IMAGE_MAX_SEGMENTS);
+    }
+
+    uint32_t next_addr = data->start_addr + sizeof(esp_image_header_t);
+    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_word);
+        if (err != ESP_OK) {
+            goto err;
         }
+        next_addr += sizeof(esp_image_segment_header_t);
+        data->segment_data[i] = next_addr;
+        next_addr += header->data_len;
+    }
+
+    // Segments all loaded, verify length
+    uint32_t end_addr = next_addr;
+    if (end_addr < data->start_addr) {
+        FAIL_LOAD("image offset has wrapped");
     }
 
+    data->image_len = end_addr - data->start_addr;
+    ESP_LOGV(TAG, "image start 0x%08x end of last section 0x%08x", data->start_addr, end_addr);
+    err = verify_checksum(sha_handle, checksum_word, data);
     if (err != ESP_OK) {
-        bzero(image_header, sizeof(esp_image_header_t));
+        goto err;
     }
-    return err;
-}
 
-esp_err_t esp_image_load_segment_header(uint8_t index, uint32_t src_addr, const esp_image_header_t *image_header, bool log_errors, esp_image_segment_header_t *segment_header, uint32_t *segment_data_offset)
-{
-    esp_err_t err = ESP_OK;
-    uint32_t next_addr = src_addr + sizeof(esp_image_header_t);
+    if (data->image_len > part->size) {
+        FAIL_LOAD("Image length %d doesn't fit in partition length %d", data->image_len, part->size);
+    }
 
-    if(index >= image_header->segment_count) {
-        if (log_errors) {
-            ESP_LOGE(TAG, "index %d higher than segment count %d", index, image_header->segment_count);
+#ifdef CONFIG_SECURE_BOOT_ENABLED
+    err = verify_secure_boot(sha_handle, data);
+    sha_handle = NULL;
+    if (err != ESP_OK) {
+        goto err;
+    }
+#else // No secure boot, but SHA-256 can be appended for basic corruption detection
+    if (sha_handle != NULL) {
+        err = verify_simple_hash(sha_handle, data);
+        sha_handle = NULL;
+        if (err != ESP_OK) {
+            goto err;
         }
-        return ESP_ERR_INVALID_ARG;
     }
+#endif
 
-    for(int i = 0; i <= index && err == ESP_OK; i++) {
-        ESP_LOGV(TAG, "loading segment header %d at offset 0x%x", i, next_addr);
-        err = bootloader_flash_read(next_addr, segment_header, sizeof(esp_image_segment_header_t), true);
-        if (err == ESP_OK) {
-            if ((segment_header->data_len & 3) != 0
-                || segment_header->data_len >= SIXTEEN_MB) {
-                if (log_errors) {
-                    ESP_LOGE(TAG, "invalid segment length 0x%x", segment_header->data_len);
+#ifdef BOOTLOADER_BUILD
+    if (do_load) { // Need to deobfuscate RAM
+        for (int i = 0; i < data->image.segment_count; i++) {
+            uint32_t load_addr = data->segments[i].load_addr;
+            if (should_load(load_addr)) {
+                uint32_t *loaded = (uint32_t *)load_addr;
+                for (int j = 0; j < data->segments[i].data_len/sizeof(uint32_t); j++) {
+                    loaded[j] ^= (j & 1) ? ram_obfs_value[0] : ram_obfs_value[1];
                 }
-                err = ESP_ERR_IMAGE_INVALID;
             }
-            next_addr += sizeof(esp_image_segment_header_t);
-            ESP_LOGV(TAG, "segment data length 0x%x data starts 0x%x", segment_header->data_len, next_addr);
-            *segment_data_offset = next_addr;
-            next_addr += segment_header->data_len;
         }
     }
+#endif
 
-    if (err != ESP_OK) {
-        *segment_data_offset = 0;
-        bzero(segment_header, sizeof(esp_image_segment_header_t));
+    // Success!
+    return ESP_OK;
+
+ err:
+    if (err == ESP_OK) {
+      err = ESP_ERR_IMAGE_INVALID;
     }
+    if (sha_handle != NULL) {
+        // Need to finish the hash process to free the handle
+        bootloader_sha256_finish(sha_handle, NULL);
+    }
+    // Prevent invalid/incomplete data leaking out
+    bzero(data, sizeof(esp_image_metadata_t));
+    return err;
+}
+
+static esp_err_t verify_image_header(uint32_t src_addr, const esp_image_header_t *image, bool silent)
+{
+    esp_err_t err = ESP_OK;
 
+    if (image->magic != ESP_IMAGE_HEADER_MAGIC) {
+        if (!silent) {
+            ESP_LOGE(TAG, "image at 0x%x has invalid magic byte", src_addr);
+        }
+        err = ESP_ERR_IMAGE_INVALID;
+    }
+    if (!silent) {
+        if (image->spi_mode > ESP_IMAGE_SPI_MODE_SLOW_READ) {
+            ESP_LOGW(TAG, "image at 0x%x has invalid SPI mode %d", src_addr, image->spi_mode);
+        }
+        if (image->spi_speed > ESP_IMAGE_SPI_SPEED_80M) {
+            ESP_LOGW(TAG, "image at 0x%x has invalid SPI speed %d", src_addr, image->spi_speed);
+        }
+        if (image->spi_size > ESP_IMAGE_FLASH_SIZE_MAX) {
+            ESP_LOGW(TAG, "image at 0x%x has invalid SPI size %d", src_addr, image->spi_size);
+        }
+    }
     return err;
 }
 
-esp_err_t esp_image_basic_verify(uint32_t src_addr, bool log_errors, uint32_t *p_length)
+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;
-    uint8_t buf[128];
-    uint8_t checksum = ESP_ROM_CHECKSUM_INITIAL;
-    esp_image_header_t image_header;
-    esp_image_segment_header_t segment_header = { 0 };
-    uint32_t segment_data_offs = 0;
-    uint32_t end_addr;
-    uint32_t length;
 
-    if (p_length != NULL) {
-        *p_length = 0;
+    /* read segment header */
+    err = bootloader_flash_read(flash_addr, header, sizeof(esp_image_segment_header_t), true);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "bootloader_flash_read failed at 0x%08x", flash_addr);
+        return err;
+    }
+    if (sha_handle != NULL) {
+        bootloader_sha256_data(sha_handle, header, sizeof(esp_image_segment_header_t));
     }
 
-    err = esp_image_load_header(src_addr, log_errors, &image_header);
+    intptr_t load_addr = header->load_addr;
+    uint32_t data_len = header->data_len;
+    uint32_t data_addr = flash_addr + sizeof(esp_image_segment_header_t);
+
+    ESP_LOGV(TAG, "segment data length 0x%x data starts 0x%x", data_len, data_addr);
+
+    err = verify_segment_header(index, header, data_addr, silent);
     if (err != ESP_OK) {
         return err;
     }
 
-    ESP_LOGD(TAG, "reading %d image segments", image_header.segment_count);
+    if (data_len % 4 != 0) {
+        FAIL_LOAD("unaligned segment length 0x%x", data_len);
+    }
 
-    /* Checksum each segment's data */
-    for (int i = 0; i < image_header.segment_count; i++) {
-        err = esp_image_load_segment_header(i, src_addr, &image_header, log_errors,
-                                      &segment_header, &segment_data_offs);
-        if (err != ESP_OK) {
-            return err;
+    bool is_mapping = should_map(load_addr);
+    do_load = do_load && should_load(load_addr);
+
+    if (!silent) {
+        ESP_LOGI(TAG, "segment %d: paddr=0x%08x vaddr=0x%08x size=0x%05x (%6d) %s",
+                 index, data_addr, load_addr,
+                 data_len, data_len,
+                 (do_load)?"load":(is_mapping)?"map":"");
+    }
+
+    if (do_load) {
+        /* Before loading segment, check it doesn't clobber bootloader RAM... */
+        uint32_t end_addr = load_addr + data_len;
+        if (end_addr < 0x40000000) {
+            intptr_t sp = (intptr_t)get_sp();
+            if (end_addr > sp - STACK_LOAD_HEADROOM) {
+                ESP_LOGE(TAG, "Segment %d end address 0x%08x too high (bootloader stack 0x%08x liimit 0x%08x)",
+                         index, end_addr, sp, sp - STACK_LOAD_HEADROOM);
+                return ESP_ERR_IMAGE_INVALID;
+            }
         }
+    }
 
-        uint32_t load_addr = segment_header.load_addr;
-        bool map_segment = (load_addr >= SOC_DROM_LOW && load_addr < SOC_DROM_HIGH)
-            || (load_addr >= SOC_IROM_LOW && load_addr < SOC_IROM_HIGH);
+    const uint32_t *data = (const uint32_t *)bootloader_mmap(data_addr, data_len);
+    if(!data) {
+        ESP_LOGE(TAG, "bootloader_mmap(0x%x, 0x%x) failed",
+                 data_addr, data_len);
+        return ESP_FAIL;
+    }
+
+#ifdef BOOTLOADER_BUILD
+    // Set up the obfuscation value to use for loading
+    while (ram_obfs_value[0] == 0 || ram_obfs_value[1] == 0) {
+        bootloader_fill_random(ram_obfs_value, sizeof(ram_obfs_value));
+    }
+    uint32_t *dest = (uint32_t *)load_addr;
+#endif
 
+    const uint32_t *src = data;
 
-        /* Check that flash cache mapped segment aligns correctly from flash it's mapped address,
-           relative to the 64KB page mapping size.
-        */
-        ESP_LOGV(TAG, "segment %d map_segment %d segment_data_offs 0x%x load_addr 0x%x",
-                 i, map_segment, segment_data_offs, load_addr);
-        if (map_segment && ((segment_data_offs % SPI_FLASH_MMU_PAGE_SIZE) != (load_addr % SPI_FLASH_MMU_PAGE_SIZE))) {
-            ESP_LOGE(TAG, "Segment %d has load address 0x%08x, conflict with segment data at 0x%08x",
-                     i, load_addr, segment_data_offs);
+    for (int i = 0; i < data_len; i += 4) {
+        int w_i = i/4; // Word index
+        uint32_t w = src[w_i];
+        *checksum ^= w;
+#ifdef BOOTLOADER_BUILD
+        if (do_load) {
+            dest[w_i] = w ^ ((w_i & 1) ? ram_obfs_value[0] : ram_obfs_value[1]);
         }
+#endif
+        // SHA_CHUNK determined experimentally as the optimum size
+        // to call bootloader_sha256_data() with. This is a bit
+        // counter-intuitive, but it's ~3ms better than using the
+        // SHA256 block size.
+        const size_t SHA_CHUNK = 1024;
+        if (sha_handle != NULL && i % SHA_CHUNK == 0) {
+            bootloader_sha256_data(sha_handle, &src[w_i],
+                                   MIN(SHA_CHUNK, data_len - i));
+        }
+    }
 
-        for (int i = 0; i < segment_header.data_len; i += sizeof(buf)) {
-            err = bootloader_flash_read(segment_data_offs + i, buf, sizeof(buf), true);
-            if (err != ESP_OK) {
-                return err;
-            }
-            for (int j = 0; j < sizeof(buf) && i + j < segment_header.data_len; j++) {
-                checksum ^= buf[j];
-            }
+    bootloader_munmap(data);
+
+    return ESP_OK;
+
+ err:
+    if (err == ESP_OK) {
+        err = ESP_ERR_IMAGE_INVALID;
+    }
+    return err;
+}
+
+static esp_err_t verify_segment_header(int index, const esp_image_segment_header_t *segment, uint32_t segment_data_offs, bool silent)
+{
+    if ((segment->data_len & 3) != 0
+        || segment->data_len >= SIXTEEN_MB) {
+        if (!silent) {
+            ESP_LOGE(TAG, "invalid segment length 0x%x", segment->data_len);
         }
+        return ESP_ERR_IMAGE_INVALID;
     }
 
-    /* End of image, verify checksum */
-    end_addr = segment_data_offs + segment_header.data_len;
+    uint32_t load_addr = segment->load_addr;
+    bool map_segment = should_map(load_addr);
 
-    if (end_addr < src_addr) {
-        if (log_errors) {
-            ESP_LOGE(TAG, "image offset has wrapped");
+    /* Check that flash cache mapped segment aligns correctly from flash to its mapped address,
+       relative to the 64KB page mapping size.
+    */
+    ESP_LOGV(TAG, "segment %d map_segment %d segment_data_offs 0x%x load_addr 0x%x",
+             index, map_segment, segment_data_offs, load_addr);
+    if (map_segment
+        && ((segment_data_offs % SPI_FLASH_MMU_PAGE_SIZE) != (load_addr % SPI_FLASH_MMU_PAGE_SIZE))) {
+        if (!silent) {
+            ESP_LOGE(TAG, "Segment %d load address 0x%08x, doesn't match data 0x%08x",
+                     index, load_addr, segment_data_offs);
         }
         return ESP_ERR_IMAGE_INVALID;
     }
 
-    length = end_addr - src_addr;
-    if (length >= SIXTEEN_MB) {
-        if (log_errors) {
-            ESP_LOGE(TAG, "invalid total length 0x%x", length);
+    return ESP_OK;
+}
+
+static bool should_map(uint32_t load_addr)
+{
+    return (load_addr >= SOC_IROM_LOW && load_addr < SOC_IROM_HIGH)
+        || (load_addr >= SOC_DROM_LOW && load_addr < SOC_DROM_HIGH);
+}
+
+static bool should_load(uint32_t load_addr)
+{
+    /* Reload the RTC memory segments whenever a non-deepsleep reset
+       is occurring */
+    bool load_rtc_memory = rtc_get_reset_reason(0) != DEEPSLEEP_RESET;
+
+    if (should_map(load_addr)) {
+        return false;
+    }
+
+    if (load_addr < 0x10000000) {
+        // Reserved for non-loaded addresses.
+        // Current reserved values are
+        // 0x0 (padding block)
+        // 0x4 (unused, but reserved for an MD5 block)
+        return false;
+    }
+
+    if (!load_rtc_memory) {
+        if (load_addr >= SOC_RTC_IRAM_LOW && load_addr < SOC_RTC_IRAM_HIGH) {
+            ESP_LOGD(TAG, "Skipping RTC code segment at 0x%08x\n", load_addr);
+            return false;
         }
+        if (load_addr >= SOC_RTC_DATA_LOW && load_addr < SOC_RTC_DATA_HIGH) {
+            ESP_LOGD(TAG, "Skipping RTC data segment at 0x%08x\n", load_addr);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+esp_err_t esp_image_verify_bootloader(uint32_t *length)
+{
+    esp_image_metadata_t data;
+    const esp_partition_pos_t bootloader_part = {
+        .offset = ESP_BOOTLOADER_OFFSET,
+        .size = ESP_PARTITION_TABLE_OFFSET - ESP_BOOTLOADER_OFFSET,
+    };
+    esp_err_t err = esp_image_load(ESP_IMAGE_VERIFY,
+                                   &bootloader_part,
+                                   &data);
+    if (length != NULL) {
+        *length = (err == ESP_OK) ? data.image_len : 0;
+    }
+    return err;
+}
+
+static esp_err_t verify_checksum(bootloader_sha256_handle_t sha_handle, uint32_t checksum_word, esp_image_metadata_t *data)
+{
+    uint32_t unpadded_length = data->image_len;
+    uint32_t length = unpadded_length + 1; // Add a byte for the checksum
+    length = (length + 15) & ~15; // Pad to next full 16 byte block
+
+    // Verify checksum
+    uint8_t buf[16];
+    esp_err_t err = bootloader_flash_read(data->start_addr + unpadded_length, buf, length - unpadded_length, true);
+    uint8_t calc = buf[length - unpadded_length - 1];
+    uint8_t checksum = (checksum_word >> 24)
+        ^ (checksum_word >> 16)
+        ^ (checksum_word >> 8)
+        ^ (checksum_word >> 0);
+    if (err != ESP_OK || checksum != calc) {
+        ESP_LOGE(TAG, "Checksum failed. Calculated 0x%x read 0x%x", checksum, calc);
         return ESP_ERR_IMAGE_INVALID;
     }
+    if (sha_handle != NULL) {
+        bootloader_sha256_data(sha_handle, buf, length - unpadded_length);
+    }
+
+    if (data->image.hash_appended) {
+        // Account for the hash in the total image length
+        length += HASH_LEN;
+    }
+    data->image_len = length;
+
+    return ESP_OK;
+}
+
+static void debug_log_hash(const uint8_t *image_hash, const char *caption);
+
+static esp_err_t verify_secure_boot(bootloader_sha256_handle_t sha_handle, esp_image_metadata_t *data)
+{
+    uint8_t image_hash[HASH_LEN] = { 0 };
+
+    // For secure boot, we calculate the signature hash over the whole file, which includes any "simple" hash
+    // appended to the image for corruption detection
+    if (data->image.hash_appended) {
+        const void *simple_hash = bootloader_mmap(data->start_addr + data->image_len - HASH_LEN, HASH_LEN);
+        bootloader_sha256_data(sha_handle, simple_hash, HASH_LEN);
+        bootloader_munmap(simple_hash);
+    }
+
+    bootloader_sha256_finish(sha_handle, image_hash);
 
-    /* image padded to next full 16 byte block, with checksum byte at very end */
-    ESP_LOGV(TAG, "unpadded image length 0x%x", length);
-    length += 16; /* always pad by at least 1 byte */
-    length = length - (length % 16);
-    ESP_LOGV(TAG, "padded image length 0x%x", length);
-    ESP_LOGD(TAG, "reading checksum block at 0x%x", src_addr + length - 16);
-    bootloader_flash_read(src_addr + length - 16, buf, 16, true);
-    if (checksum != buf[15]) {
-        if (log_errors) {
-            ESP_LOGE(TAG, "checksum failed. Calculated 0x%x read 0x%x",
-                     checksum, buf[15]);
+    // Log the hash for debugging
+    debug_log_hash(image_hash, "Calculated secure boot hash");
+
+    // Use hash to verify signature block
+    const esp_secure_boot_sig_block_t *sig_block = bootloader_mmap(data->start_addr + data->image_len, sizeof(esp_secure_boot_sig_block_t));
+    esp_err_t err = esp_secure_boot_verify_signature_block(sig_block, image_hash);
+    bootloader_munmap(sig_block);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "Secure boot signature verification failed");
+
+        // Go back and check if the simple hash matches or not (we're off the fast path so we can re-hash the whole image now)
+        ESP_LOGI(TAG, "Calculating simple hash to check for corruption...");
+        const void *whole_image = bootloader_mmap(data->start_addr, data->image_len - HASH_LEN);
+        if (whole_image != NULL) {
+            sha_handle = bootloader_sha256_start();
+            bootloader_sha256_data(sha_handle, whole_image, data->image_len - HASH_LEN);
+            bootloader_munmap(whole_image);
+            if (verify_simple_hash(sha_handle, data) != ESP_OK) {
+                ESP_LOGW(TAG, "image corrupted on flash");
+            } else {
+                ESP_LOGW(TAG, "image valid, signature bad");
+            }
         }
         return ESP_ERR_IMAGE_INVALID;
     }
 
-    if (p_length != NULL) {
-        *p_length = length;
+    return ESP_OK;
+}
+
+static esp_err_t verify_simple_hash(bootloader_sha256_handle_t sha_handle, esp_image_metadata_t *data)
+{
+    uint8_t image_hash[HASH_LEN] = { 0 };
+    bootloader_sha256_finish(sha_handle, image_hash);
+
+    // Log the hash for debugging
+    debug_log_hash(image_hash, "Calculated hash");
+
+    // Simple hash for verification only
+    const void *hash = bootloader_mmap(data->start_addr + data->image_len - HASH_LEN, HASH_LEN);
+    if (memcmp(hash, image_hash, HASH_LEN) != 0) {
+        ESP_LOGE(TAG, "Image hash failed - image is corrupt");
+        debug_log_hash(hash, "Expected hash");
+        bootloader_munmap(hash);
+        return ESP_ERR_IMAGE_INVALID;
     }
+
+    bootloader_munmap(hash);
     return ESP_OK;
 }
+
+// Log a hash as a hex string
+static void debug_log_hash(const uint8_t *image_hash, const char *label)
+{
+#if BOOT_LOG_LEVEL >= LOG_LEVEL_DEBUG
+        char hash_print[sizeof(image_hash)*2 + 1];
+        hash_print[sizeof(image_hash)*2] = 0;
+        for (int i = 0; i < sizeof(image_hash); i++) {
+            for (int shift = 0; shift < 2; shift++) {
+                uint8_t nibble = (image_hash[i] >> (shift ? 0 : 4)) & 0x0F;
+                if (nibble < 10) {
+                    hash_print[i*2+shift] = '0' + nibble;
+                } else {
+                    hash_print[i*2+shift] = 'a' + nibble - 10;
+                }
+            }
+        }
+        ESP_LOGD(TAG, "%s: %s", label, hash_print);
+#endif
+}

+ 8 - 14
components/bootloader_support/src/flash_encrypt.c

@@ -210,8 +210,8 @@ static esp_err_t encrypt_bootloader()
 {
     esp_err_t err;
     uint32_t image_length;
-    /* Check for plaintext bootloader */
-    if (esp_image_basic_verify(ESP_BOOTLOADER_OFFSET, false, &image_length) == ESP_OK) {
+    /* Check for plaintext bootloader (verification will fail if it's already encrypted) */
+    if (esp_image_verify_bootloader(&image_length) == ESP_OK) {
         ESP_LOGD(TAG, "bootloader is plaintext. Encrypting...");
         err = esp_flash_encrypt_region(ESP_BOOTLOADER_OFFSET, image_length);
         if (err != ESP_OK) {
@@ -270,21 +270,15 @@ static esp_err_t encrypt_and_load_partition_table(esp_partition_info_t *partitio
 static esp_err_t encrypt_partition(int index, const esp_partition_info_t *partition)
 {
     esp_err_t err;
-    uint32_t image_len = partition->pos.size;
     bool should_encrypt = (partition->flags & PART_FLAG_ENCRYPTED);
 
     if (partition->type == PART_TYPE_APP) {
-        /* check if the partition holds an unencrypted app */
-        if (esp_image_basic_verify(partition->pos.offset, false, &image_len) == ESP_OK) {
-            if(image_len > partition->pos.size) {
-                ESP_LOGE(TAG, "partition entry %d has image longer than partition (%d vs %d)", index, image_len, partition->pos.size);
-                should_encrypt = false;
-            } else {
-                should_encrypt = true;
-            }
-        } else {
-            should_encrypt = false;
-        }
+      /* check if the partition holds a valid unencrypted app */
+      esp_image_metadata_t data_ignored;
+      err = esp_image_load(ESP_IMAGE_VERIFY,
+                           &partition->pos,
+                           &data_ignored);
+      should_encrypt = (err == ESP_OK);
     } else if (partition->type == PART_TYPE_DATA && partition->subtype == PART_SUBTYPE_DATA_OTA) {
         /* check if we have ota data partition and the partition should be encrypted unconditionally */
         should_encrypt = true;

+ 2 - 2
components/bootloader_support/src/secure_boot.c

@@ -67,7 +67,7 @@ static bool secure_boot_generate(uint32_t image_len){
     }
 
     /* generate digest from image contents */
-    image = bootloader_mmap(0x1000, image_len);
+    image = bootloader_mmap(ESP_BOOTLOADER_OFFSET, image_len);
     if (!image) {
         ESP_LOGE(TAG, "bootloader_mmap(0x1000, 0x%x) failed", image_len);
         return false;
@@ -111,7 +111,7 @@ esp_err_t esp_secure_boot_permanently_enable(void) {
         return ESP_OK;
     }
 
-    err = esp_image_basic_verify(0x1000, true, &image_len);
+    err = esp_image_verify_bootloader(&image_len);
     if (err != ESP_OK) {
         ESP_LOGE(TAG, "bootloader image appears invalid! error %d", err);
         return err;

+ 30 - 39
components/bootloader_support/src/secure_boot_signatures.c

@@ -14,6 +14,7 @@
 #include "sdkconfig.h"
 
 #include "bootloader_flash.h"
+#include "bootloader_sha.h"
 #include "esp_log.h"
 #include "esp_image_format.h"
 #include "esp_secure_boot.h"
@@ -34,20 +35,13 @@ extern const uint8_t signature_verification_key_end[] asm("_binary_signature_ver
 
 #define SIGNATURE_VERIFICATION_KEYLEN 64
 
+#define DIGEST_LEN 32
+
 esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length)
 {
-#ifdef BOOTLOADER_BUILD
-    SHA_CTX sha;
-#endif
-    uint8_t digest[32];
-    ptrdiff_t keylen;
+    uint8_t digest[DIGEST_LEN];
     const uint8_t *data;
     const esp_secure_boot_sig_block_t *sigblock;
-    bool is_valid;
-#ifdef BOOTLOADER_BUILD
-    const uint8_t *digest_data;
-    uint32_t digest_len;
-#endif
 
     ESP_LOGD(TAG, "verifying signature src_addr 0x%x length 0x%x", src_addr, length);
 
@@ -57,46 +51,43 @@ esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length)
         return ESP_FAIL;
     }
 
-    sigblock = (const esp_secure_boot_sig_block_t *)(data + length);
-
-    if (sigblock->version != 0) {
-        ESP_LOGE(TAG, "src 0x%x has invalid signature version field 0x%08x", src_addr, sigblock->version);
-        goto unmap_and_fail;
-    }
-
+    // Calculate digest of main image
 #ifdef BOOTLOADER_BUILD
-    /* Use ROM SHA functions directly */
-    ets_sha_enable();
-    ets_sha_init(&sha);
-    digest_len = length * 8;
-    digest_data = data;
-    while (digest_len > 0) {
-        uint32_t chunk_len = (digest_len > 64) ? 64 : digest_len;
-        ets_sha_update(&sha, SHA2_256, digest_data, chunk_len);
-        digest_len -= chunk_len;
-        digest_data += chunk_len / 8;
-    }
-    ets_sha_finish(&sha, SHA2_256, digest);
-    ets_sha_disable();
+    bootloader_sha256_handle_t handle = bootloader_sha256_start();
+    bootloader_sha256_data(handle, data, length);
+    bootloader_sha256_finish(handle, digest);
 #else
     /* Use thread-safe esp-idf SHA function */
     esp_sha(SHA2_256, data, length, digest);
 #endif
 
+    // Map the signature block and verify the signature
+    sigblock = (const esp_secure_boot_sig_block_t *)(data + length);
+    esp_err_t err = esp_secure_boot_verify_signature_block(sigblock, digest);
+    bootloader_munmap(data);
+    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)
+{
+    ptrdiff_t keylen;
+    bool is_valid;
+
     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);
-        goto unmap_and_fail;
+        return ESP_FAIL;
     }
 
-    is_valid = uECC_verify(signature_verification_key_start,
-                           digest, sizeof(digest), sigblock->signature,
-                           uECC_secp256r1());
+    if (sig_block->version != 0) {
+        ESP_LOGE(TAG, "image has invalid signature version field 0x%08x", sig_block->version);
+        return ESP_FAIL;
+    }
 
-    bootloader_munmap(data);
+    is_valid = uECC_verify(signature_verification_key_start,
+                                image_digest,
+                                DIGEST_LEN,
+                                sig_block->signature,
+                                uECC_secp256r1());
     return is_valid ? ESP_OK : ESP_ERR_IMAGE_INVALID;
-
- unmap_and_fail:
-    bootloader_munmap(data);
-    return ESP_FAIL;
 }

+ 20 - 8
components/bootloader_support/test/test_verify_image.c

@@ -1,5 +1,5 @@
 /*
- * Tests for bootloader_support esp_image_basic_verify()
+ * Tests for bootloader_support esp_load(ESP_IMAGE_VERIFY, ...)
  */
 
 #include <esp_types.h>
@@ -19,19 +19,31 @@
 
 TEST_CASE("Verify bootloader image in flash", "[bootloader_support]")
 {
-    uint32_t image_len = 0;
-    TEST_ASSERT_EQUAL_HEX(ESP_OK, esp_image_basic_verify(0x1000, true, &image_len));
-    TEST_ASSERT_NOT_EQUAL(0, image_len);
+    const esp_partition_pos_t fake_bootloader_partition = {
+        .offset = ESP_BOOTLOADER_OFFSET,
+        .size = ESP_PARTITION_TABLE_OFFSET - ESP_BOOTLOADER_OFFSET,
+    };
+    esp_image_metadata_t data = { 0 };
+    TEST_ASSERT_EQUAL_HEX(ESP_OK, esp_image_load(ESP_IMAGE_VERIFY, &fake_bootloader_partition, &data));
+    TEST_ASSERT_NOT_EQUAL(0, data.image_len);
+
+    uint32_t bootloader_length = 0;
+    TEST_ASSERT_EQUAL_HEX(ESP_OK, esp_image_verify_bootloader(&bootloader_length));
+    TEST_ASSERT_EQUAL(data.image_len, bootloader_length);
 }
 
 TEST_CASE("Verify unit test app image", "[bootloader_support]")
 {
-    uint32_t image_len = 0;
+    esp_image_metadata_t data = { 0 };
     const esp_partition_t *running = esp_ota_get_running_partition();
     TEST_ASSERT_NOT_EQUAL(NULL, running);
+    const esp_partition_pos_t running_pos  = {
+        .offset = running->address,
+        .size = running->size,
+    };
 
-    TEST_ASSERT_EQUAL_HEX(ESP_OK, esp_image_basic_verify(running->address, true, &image_len));
-    TEST_ASSERT_NOT_EQUAL(0, image_len);
-    TEST_ASSERT_TRUE(image_len <= running->size);
+    TEST_ASSERT_EQUAL_HEX(ESP_OK, esp_image_load(ESP_IMAGE_VERIFY, &running_pos, &data));
+    TEST_ASSERT_NOT_EQUAL(0, data.image_len);
+    TEST_ASSERT_TRUE(data.image_len <= running->size);
 }
 

+ 1 - 1
components/esptool_py/esptool

@@ -1 +1 @@
-Subproject commit 325f01637b667af02cc6390965b09d50e6a31dac
+Subproject commit a4207741eca1fb8e5e3670e498ed058320bbcb5a

+ 2 - 0
components/soc/esp32/include/soc/dport_reg.h

@@ -4261,7 +4261,9 @@
 /* Flash MMU table for APP CPU */
 #define DPORT_APP_FLASH_MMU_TABLE ((volatile uint32_t*) 0x3FF12000)
 
+#define DPORT_FLASH_MMU_TABLE_SIZE 0x100
 
+#define DPORT_FLASH_MMU_TABLE_INVALID_VAL 0x100
 
 #endif /*_SOC_DPORT_REG_H_ */
 

+ 4 - 4
components/spi_flash/flash_mmap.c

@@ -83,14 +83,14 @@ static void IRAM_ATTR spi_flash_mmap_init()
         uint32_t entry_app = DPORT_APP_FLASH_MMU_TABLE[i];
         if (entry_pro != entry_app) {
             // clean up entries used by boot loader
-            entry_pro = INVALID_ENTRY_VAL;
-            DPORT_PRO_FLASH_MMU_TABLE[i] = INVALID_ENTRY_VAL;
+            entry_pro = DPORT_FLASH_MMU_TABLE_INVALID_VAL;
+            DPORT_PRO_FLASH_MMU_TABLE[i] = DPORT_FLASH_MMU_TABLE_INVALID_VAL;
         }
         if ((entry_pro & INVALID_ENTRY_VAL) == 0 && (i == 0 || i == PRO_IRAM0_FIRST_USABLE_PAGE || entry_pro != 0)) {
             s_mmap_page_refcnt[i] = 1;
         } else {
-            DPORT_PRO_FLASH_MMU_TABLE[i] = INVALID_ENTRY_VAL;
-            DPORT_APP_FLASH_MMU_TABLE[i] = INVALID_ENTRY_VAL;
+            DPORT_PRO_FLASH_MMU_TABLE[i] = DPORT_FLASH_MMU_TABLE_INVALID_VAL;
+            DPORT_APP_FLASH_MMU_TABLE[i] = DPORT_FLASH_MMU_TABLE_INVALID_VAL;
         }
     }
 }

+ 1 - 1
make/project.mk

@@ -290,7 +290,7 @@ export HOSTCC HOSTLD HOSTAR HOSTOBJCOPY SIZE
 CC := $(call dequote,$(CONFIG_TOOLPREFIX))gcc
 CXX := $(call dequote,$(CONFIG_TOOLPREFIX))c++
 LD := $(call dequote,$(CONFIG_TOOLPREFIX))ld
-AR := $(call dequote,$(CONFIG_TOOLPREFIX))ar
+AR := $(call dequote,$(CONFIG_TOOLPREFIX))gcc-ar
 OBJCOPY := $(call dequote,$(CONFIG_TOOLPREFIX))objcopy
 SIZE := $(call dequote,$(CONFIG_TOOLPREFIX))size
 export CC CXX LD AR OBJCOPY SIZE

+ 1 - 0
tools/unit-test-app/sdkconfig

@@ -19,6 +19,7 @@ CONFIG_LOG_BOOTLOADER_LEVEL_WARN=y
 # CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set
 # CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set
 CONFIG_LOG_BOOTLOADER_LEVEL=2
+# CONFIG_BOOTLOADER_LTO is not set
 
 #
 # Security features