From: Angus Gratton Date: Fri, 16 Jun 2017 06:30:21 +0000 (+1000) Subject: bootloader: Combine loading from flash & verifying to save boot time X-Git-Tag: v3.1-dev~454^2~6 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=0c8888d68f9ee38b549e0895a93672dbeb2b14e3;p=esp-idf bootloader: Combine loading from flash & verifying to save boot time Still needs updating to account for secure boot. --- diff --git a/components/app_update/esp_ota_ops.c b/components/app_update/esp_ota_ops.c index ab8342baa8..a9247bf51e 100644 --- a/components/app_update/esp_ota_ops.c +++ b/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_length); 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_length); if (ret != ESP_OK) { return ESP_ERR_OTA_VALIDATE_FAILED; } diff --git a/components/bootloader/subproject/main/bootloader_start.c b/components/bootloader/subproject/main/bootloader_start.c index 123f67d8f6..75f481ba27 100644 --- a/components/bootloader/subproject/main/bootloader_start.c +++ b/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,22 @@ 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 */ + int *sp = get_sp(); + assert(&_bss_start <= &_bss_end); + assert(&_data_start <= &_data_end); + assert(sp < &_bss_start); + assert(sp < &_data_start); + //Clear bss memset(&_bss_start, 0, (&_bss_end - &_bss_start) * sizeof(_bss_start)); @@ -253,7 +266,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 +289,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,11 +422,10 @@ 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 */ + 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; @@ -420,8 +433,8 @@ static void unpack_load_app(const esp_partition_pos_t* partition) #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); + ESP_LOGI(TAG, "Verifying app signature @ 0x%x (length 0x%x)", partition->offset, data.image_length); + err = esp_secure_boot_verify_signature(partition->offset, data.image_length); if (err != ESP_OK) { ESP_LOGE(TAG, "App image @ 0x%x failed signature verification (%d)", partition->offset, err); return; @@ -430,11 +443,6 @@ static void unpack_load_app(const esp_partition_pos_t* partition) } #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 +450,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( @@ -632,7 +555,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 +785,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) {} +} diff --git a/components/bootloader/subproject/main/esp32.bootloader.ld b/components/bootloader/subproject/main/esp32.bootloader.ld index 500478814c..3f1b9eb0b6 100644 --- a/components/bootloader/subproject/main/esp32.bootloader.ld +++ b/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 @@ -66,7 +55,6 @@ SECTIONS _bss_end = ABSOLUTE(.); } >dram_seg - .dram0.data : { _data_start = ABSOLUTE(.); @@ -84,15 +72,11 @@ SECTIONS _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 } diff --git a/components/bootloader_support/include/esp_image_format.h b/components/bootloader_support/include/esp_image_format.h index e0759c3c0e..97afafc3f0 100644 --- a/components/bootloader_support/include/esp_image_format.h +++ b/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 #include +#include "esp_flash_partitions.h" #define ESP_ERR_IMAGE_BASE 0x2000 #define ESP_ERR_IMAGE_FLASH_FAIL (ESP_ERR_IMAGE_BASE + 1) @@ -73,62 +73,56 @@ 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_length; -/** - * @brief Read the segment header and data offset of a segment in the 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. - * - * @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. - */ -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_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 Non-cryptographically validate app image integrity. On success, length of image is provided to caller. + * @brief Verify and (optionally, in bootloader mode) load an app image. * - * If the image has a secure boot signature appended, the signature is not checked and this length is not included in the - * output value. + * If encryption is enabled, data will be transparently decrypted. + * + * @param part Partition to load the app from. + * @param[out] data Pointer to the metadata structure to 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. + * @param log_errors Log errors reading the image. * * Image validation checks: * - Magic byte - * - No single segment longer than 16MB - * - Total image no longer than 16MB + * - Partition smaller than 16MB + * - All segments & image fit in partition * - 8 bit image checksum is valid + * - (Signature) if signature verification is enabled * - * 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 metadata was loaded successfully, 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 data pointer is invalid. + */ +esp_err_t esp_image_load(esp_image_load_mode_t mode, const esp_partition_pos_t *part, esp_image_metadata_t *data); + +/** + * @brief Verify the bootloader image. * - * @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 +133,3 @@ typedef struct { uint32_t irom_load_addr; uint32_t irom_size; } esp_image_flash_mapping_t; - -#endif diff --git a/components/bootloader_support/src/bootloader_flash.c b/components/bootloader_support/src/bootloader_flash.c index 931fa94126..0432d755c2 100644 --- a/components/bootloader_support/src/bootloader_flash.c +++ b/components/bootloader_support/src/bootloader_flash.c @@ -88,6 +88,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) diff --git a/components/bootloader_support/src/esp_image_format.c b/components/bootloader_support/src/esp_image_format.c index e6c5f899c1..a91983293d 100644 --- a/components/bootloader_support/src/esp_image_format.c +++ b/components/bootloader_support/src/esp_image_format.c @@ -13,6 +13,8 @@ // limitations under the License. #include +#include +#include #include #include #include @@ -22,168 +24,314 @@ static const char *TAG = "esp_image"; #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 4096 + +/* 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, 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) + +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; - 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 (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); - } - } + 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) { - 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); + 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); - if(index >= image_header->segment_count) { - if (log_errors) { - ESP_LOGE(TAG, "index %d higher than segment count %d", index, image_header->segment_count); - } - return ESP_ERR_INVALID_ARG; + 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); } - for(int i = 0; i <= index && err == ESP_OK; i++) { + uint32_t next_addr = data->start_addr + sizeof(esp_image_header_t); + for(int i = 0; i < data->image.segment_count && err == ESP_OK; 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 = 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); - } - 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; + err = process_segment(i, next_addr, header, silent, do_load, &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; } - if (err != ESP_OK) { - *segment_data_offset = 0; - bzero(segment_header, sizeof(esp_image_segment_header_t)); + // Segments all loaded, verify length + uint32_t end_addr = next_addr; + if (end_addr < data->start_addr) { + FAIL_LOAD("image offset has wrapped"); } + uint32_t length = end_addr - data->start_addr; + length = length + 1; // Add a byte for the checksum + length = (length + 15) & ~15; // Pad to next full 16 byte block + if (length > part->size) { + FAIL_LOAD("Image length %d doesn't fit in partition length %d", length, part->size); + } + + // Verify checksum + uint32_t buf[16/sizeof(uint32_t)]; + err = bootloader_flash_read(data->start_addr + length - 16, buf, 16, true); + uint8_t calc = ((uint8_t *)buf)[15]; + uint8_t checksum = (checksum_word >> 24) + ^ (checksum_word >> 16) + ^ (checksum_word >> 8) + ^ (checksum_word >> 0); + if (err != ESP_OK || checksum != calc) { + FAIL_LOAD("Checksum failed. Calculated 0x%x read 0x%x", + checksum, calc); + } + + data->image_length = length; + + // Success! + return ESP_OK; + + err: + if (err == ESP_OK) { + err = ESP_ERR_IMAGE_INVALID; + } + // Prevent invalid/incomplete data leaking out + bzero(data, sizeof(esp_image_metadata_t)); return err; } -esp_err_t esp_image_basic_verify(uint32_t src_addr, bool log_errors, uint32_t *p_length) +static esp_err_t verify_image_header(uint32_t src_addr, const esp_image_header_t *image, bool silent) { - 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; + esp_err_t err = ESP_OK; - if (p_length != NULL) { - *p_length = 0; + 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; +} + +static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segment_header_t *header, bool silent, bool do_load, uint32_t *checksum) +{ + esp_err_t err; - err = esp_image_load_header(src_addr, log_errors, &image_header); + /* read segment header */ + err = bootloader_flash_read(flash_addr, header, sizeof(esp_image_segment_header_t), true); if (err != ESP_OK) { return err; } - ESP_LOGD(TAG, "reading %d image segments", image_header.segment_count); + 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); - /* 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; - } + ESP_LOGV(TAG, "segment data length 0x%x data starts 0x%x", data_len, data_addr); - 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); + err = verify_segment_header(index, header, data_addr, silent); + if (err != ESP_OK) { + return err; + } + if (data_len % 4 != 0) { + FAIL_LOAD("unaligned segment length 0x%x", data_len); + } - /* 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); - } + bool is_mapping = should_map(load_addr); + do_load = do_load && should_load(load_addr); - 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]; + 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; } } } - /* End of image, verify checksum */ - end_addr = segment_data_offs + segment_header.data_len; + const void *data = 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; + } + const uint32_t *checksum_from; + if (do_load) { + memcpy((void *)load_addr, data, data_len); + checksum_from = (const uint32_t *)load_addr; + } else { + checksum_from = (const uint32_t *)data; + } + // Update checksum, either from RAM we just loaded or from flash + for (const uint32_t *c = checksum_from; + c < checksum_from + (data_len/sizeof(uint32_t)); + c++) { + *checksum ^= *c; + } + + bootloader_munmap(data); + + return ESP_OK; + + err: + if (err == ESP_OK) { + err = ESP_ERR_IMAGE_INVALID; + } + return err; +} - if (end_addr < src_addr) { - if (log_errors) { - ESP_LOGE(TAG, "image offset has wrapped"); +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; } - length = end_addr - src_addr; - if (length >= SIXTEEN_MB) { - if (log_errors) { - ESP_LOGE(TAG, "invalid total length 0x%x", length); + uint32_t load_addr = segment->load_addr; + + /* 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 (should_map(load_addr) + && ((segment_data_offs % SPI_FLASH_MMU_PAGE_SIZE) != (load_addr % SPI_FLASH_MMU_PAGE_SIZE))) { + if (!silent) { + ESP_LOGE(TAG, "Segment %d has load address 0x%08x, doesn't match segment data at 0x%08x", + index, load_addr, segment_data_offs); } return ESP_ERR_IMAGE_INVALID; } - /* 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]); + 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 ESP_ERR_IMAGE_INVALID; } - if (p_length != NULL) { - *p_length = length; + 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_length : 0; } - return ESP_OK; + return err; } diff --git a/components/bootloader_support/src/flash_encrypt.c b/components/bootloader_support/src/flash_encrypt.c index 8ba068d03b..eff15b7ca4 100644 --- a/components/bootloader_support/src/flash_encrypt.c +++ b/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; diff --git a/components/bootloader_support/src/secure_boot.c b/components/bootloader_support/src/secure_boot.c index 0230e85ad5..ef9744ffc8 100644 --- a/components/bootloader_support/src/secure_boot.c +++ b/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;