From 0c8888d68f9ee38b549e0895a93672dbeb2b14e3 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 16 Jun 2017 16:30:21 +1000 Subject: [PATCH 1/7] bootloader: Combine loading from flash & verifying to save boot time Still needs updating to account for secure boot. --- components/app_update/esp_ota_ops.c | 21 +- .../subproject/main/bootloader_start.c | 183 +++------ .../subproject/main/esp32.bootloader.ld | 44 +- .../include/esp_image_format.h | 84 ++-- .../bootloader_support/src/bootloader_flash.c | 1 + .../bootloader_support/src/esp_image_format.c | 376 ++++++++++++------ .../bootloader_support/src/flash_encrypt.c | 22 +- .../bootloader_support/src/secure_boot.c | 4 +- 8 files changed, 396 insertions(+), 339 deletions(-) diff --git a/components/app_update/esp_ota_ops.c b/components/app_update/esp_ota_ops.c index ab8342baa8a..a9247bf51ee 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 123f67d8f65..75f481ba274 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 500478814c7..3f1b9eb0b62 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 e0759c3c0e4..97afafc3f07 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 931fa94126b..0432d755c2c 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 e6c5f899c16..a91983293d2 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 8ba068d03b7..eff15b7ca42 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 0230e85ad53..ef9744ffc8b 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; From 8f6134dd96fc16805e7a7fccd289da01f5a25f04 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 21 Jun 2017 17:39:15 +1000 Subject: [PATCH 2/7] bootloader: Obfuscate loaded memory until verification is complete --- .../bootloader_support/src/esp_image_format.c | 58 ++++++++++++++----- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/components/bootloader_support/src/esp_image_format.c b/components/bootloader_support/src/esp_image_format.c index a91983293d2..ef2aca8f5b5 100644 --- a/components/bootloader_support/src/esp_image_format.c +++ b/components/bootloader_support/src/esp_image_format.c @@ -18,6 +18,7 @@ #include #include #include +#include static const char *TAG = "esp_image"; @@ -27,6 +28,13 @@ static const char *TAG = "esp_image"; /* Headroom to ensure between stack SP (at time of checking) and data loaded from flash */ #define STACK_LOAD_HEADROOM 4096 +#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 */ @@ -98,7 +106,7 @@ goto err; } 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++) { + 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, &checksum_word); @@ -138,6 +146,20 @@ goto err; data->image_length = length; +#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]; + } + } + } + } +#endif + // Success! return ESP_OK; @@ -222,24 +244,31 @@ static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segme } } - const void *data = bootloader_mmap(data_addr, data_len); + 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; } - 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; + +#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)); } - // 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; + uint32_t *dest = (uint32_t *)load_addr; +#endif + + const uint32_t *src = data; + + for (int i = 0; i < data_len/sizeof(uint32_t); i++) { + uint32_t w = src[i]; + *checksum ^= w; +#ifdef BOOTLOADER_BUILD + if (do_load) { + dest[i] = w ^ ((i & 1) ? ram_obfs_value[0] : ram_obfs_value[1]); + } +#endif } bootloader_munmap(data); @@ -264,13 +293,14 @@ static esp_err_t verify_segment_header(int index, const esp_image_segment_header } uint32_t load_addr = segment->load_addr; + bool map_segment = should_map(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) + 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 has load address 0x%08x, doesn't match segment data at 0x%08x", From 43b99edf2b6f23dc7d0011c257fb11e8d04edb71 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 27 Jun 2017 17:25:30 +1000 Subject: [PATCH 3/7] bootloader: Calculate SHA-256 of image while loading/verifying --- .../include/esp_image_format.h | 3 +- .../include/esp_secure_boot.h | 10 +- .../include_priv/bootloader_sha.h | 32 ++++ .../bootloader_support/src/bootloader_flash.c | 6 +- .../bootloader_support/src/bootloader_sha.c | 156 ++++++++++++++++++ .../bootloader_support/src/esp_image_format.c | 68 ++++++-- .../test/test_verify_image.c | 28 +++- 7 files changed, 278 insertions(+), 25 deletions(-) create mode 100644 components/bootloader_support/include_priv/bootloader_sha.h create mode 100644 components/bootloader_support/src/bootloader_sha.c diff --git a/components/bootloader_support/include/esp_image_format.h b/components/bootloader_support/include/esp_image_format.h index 97afafc3f07..2e49252aa5e 100644 --- a/components/bootloader_support/include/esp_image_format.h +++ b/components/bootloader_support/include/esp_image_format.h @@ -81,8 +81,7 @@ typedef struct { 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; - + uint32_t image_len; /* Length of image on flash, in bytes */ } esp_image_metadata_t; /* Mode selection for esp_image_load() */ diff --git a/components/bootloader_support/include/esp_secure_boot.h b/components/bootloader_support/include/esp_secure_boot.h index 8e33a8b4609..003328557d3 100644 --- a/components/bootloader_support/include/esp_secure_boot.h +++ b/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 #include #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. @@ -88,4 +91,7 @@ typedef struct { uint8_t digest[64]; } esp_secure_boot_iv_digest_t; + +#ifdef __cplusplus +} #endif diff --git a/components/bootloader_support/include_priv/bootloader_sha.h b/components/bootloader_support/include_priv/bootloader_sha.h new file mode 100644 index 00000000000..0434000d080 --- /dev/null +++ b/components/bootloader_support/include_priv/bootloader_sha.h @@ -0,0 +1,32 @@ +// Copyright 2015-2016 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 +#include + +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); diff --git a/components/bootloader_support/src/bootloader_flash.c b/components/bootloader_support/src/bootloader_flash.c index 0432d755c2c..9a82590393e 100644 --- a/components/bootloader_support/src/bootloader_flash.c +++ b/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) diff --git a/components/bootloader_support/src/bootloader_sha.c b/components/bootloader_support/src/bootloader_sha.c new file mode 100644 index 00000000000..dc029d9f26b --- /dev/null +++ b/components/bootloader_support/src/bootloader_sha.c @@ -0,0 +1,156 @@ +// Copyright 2015-2016 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 +#include +#include +#include + +#ifndef BOOTLOADER_BUILD +// App version is a wrapper around mbedTLS SHA API +#include + +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; + mbedtls_sha256_finish(ctx, digest); +} + +#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)); + +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); + + uint32_t data_words = words_hashed; + ets_printf("Padding from %d bytes\n", data_words * 4); + + // Pad to a 60 byte long block loaded in the engine + // (normally end of block is a 64-bit length, but we know + // the upper 32 bits will be zeroes.) + int block_bytes = (words_hashed % BLOCK_WORDS) * 4; + int pad_bytes = 60 - block_bytes; + if (pad_bytes < 0) { + pad_bytes += 64; + } + static const uint8_t padding[64] = { 0x80, 0, }; + + bootloader_sha256_data(handle, padding, pad_bytes); + + assert(words_hashed % BLOCK_WORDS == 56/4); + + // 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); + + ets_printf("Padded to %d bytes\n", words_hashed * 4); + + 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 < BLOCK_WORDS; i++) { + digest_words[i] = __builtin_bswap32(sha_text_reg[i]); + } + asm volatile ("memw"); +} + +#endif diff --git a/components/bootloader_support/src/esp_image_format.c b/components/bootloader_support/src/esp_image_format.c index ef2aca8f5b5..aac9275a682 100644 --- a/components/bootloader_support/src/esp_image_format.c +++ b/components/bootloader_support/src/esp_image_format.c @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. #include +#include #include #include @@ -19,6 +20,7 @@ #include #include #include +#include static const char *TAG = "esp_image"; @@ -41,7 +43,7 @@ static bool should_load(uint32_t load_addr); 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); +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); @@ -69,6 +71,8 @@ esp_err_t esp_image_load(esp_image_load_mode_t mode, const esp_partition_pos_t * 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; + uint8_t image_digest[32] = { 0 }; if (data == NULL || part == NULL) { return ESP_ERR_INVALID_ARG; @@ -82,11 +86,17 @@ esp_err_t esp_image_load(esp_image_load_mode_t mode, const esp_partition_pos_t * bzero(data, sizeof(esp_image_metadata_t)); data->start_addr = part->offset; + sha_handle = bootloader_sha256_start(); + if (sha_handle == NULL) { + return ESP_ERR_NO_MEM; + } + 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; } + 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, @@ -109,7 +119,7 @@ goto err; 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, &checksum_word); + err = process_segment(i, next_addr, header, silent, do_load, sha_handle, &checksum_word); if (err != ESP_OK) { goto err; } @@ -124,8 +134,8 @@ goto err; FAIL_LOAD("image offset has wrapped"); } - uint32_t length = end_addr - data->start_addr; - length = length + 1; // Add a byte for the checksum + uint32_t unpadded_length = end_addr - data->start_addr; + uint32_t length = unpadded_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); @@ -133,8 +143,8 @@ goto err; // 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]; + err = bootloader_flash_read(end_addr, buf, length - unpadded_length, true); + uint8_t calc = ((uint8_t *)buf)[length - unpadded_length - 1]; uint8_t checksum = (checksum_word >> 24) ^ (checksum_word >> 16) ^ (checksum_word >> 8) @@ -144,6 +154,27 @@ goto err; checksum, calc); } + bootloader_sha256_data(sha_handle, buf, length - unpadded_length); + bootloader_sha256_finish(sha_handle, image_digest); + +#if BOOT_LOG_LEVEL >= LOG_LEVEL_DEBUG + char digest_print[sizeof(image_digest)*2 + 1]; + digest_print[sizeof(image_digest)*2] = 0; + for (int i = 0; i < sizeof(image_digest); i++) { + for (int shift = 0; shift < 2; shift++) { + uint8_t nibble = (image_digest[i] >> (shift ? 0 : 4)) & 0x0F; + if (nibble < 10) { + digest_print[i*2+shift] = '0' + nibble; + } else { + digest_print[i*2+shift] = 'a' + nibble - 10; + } + } + } + ESP_LOGD(TAG, "Total image length %d bytes (unpagged %d)", length, unpadded_length); + ESP_LOGD(TAG, "Image SHA256 digest: %s", digest_print); +#endif + // Verify digest here + data->image_length = length; #ifdef BOOTLOADER_BUILD @@ -167,6 +198,10 @@ goto err; if (err == ESP_OK) { err = ESP_ERR_IMAGE_INVALID; } + if (sha_handle != NULL) { + // Need to finish the digest process to free the handle + bootloader_sha256_finish(sha_handle, image_digest); + } // Prevent invalid/incomplete data leaking out bzero(data, sizeof(esp_image_metadata_t)); return err; @@ -196,7 +231,7 @@ static esp_err_t verify_image_header(uint32_t src_addr, const esp_image_header_t 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) +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; @@ -205,6 +240,7 @@ static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segme if (err != ESP_OK) { return err; } + bootloader_sha256_data(sha_handle, header, sizeof(esp_image_segment_header_t)); intptr_t load_addr = header->load_addr; uint32_t data_len = header->data_len; @@ -261,14 +297,24 @@ static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segme const uint32_t *src = data; - for (int i = 0; i < data_len/sizeof(uint32_t); i++) { - uint32_t w = src[i]; + 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[i] = w ^ ((i & 1) ? ram_obfs_value[0] : ram_obfs_value[1]); + 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 (i % SHA_CHUNK == 0) { + bootloader_sha256_data(sha_handle, &src[w_i], + MIN(SHA_CHUNK, data_len - i)); + } } bootloader_munmap(data); @@ -361,7 +407,7 @@ esp_err_t esp_image_verify_bootloader(uint32_t *length) &bootloader_part, &data); if (length != NULL) { - *length = (err == ESP_OK) ? data.image_length : 0; + *length = (err == ESP_OK) ? data.image_len : 0; } return err; } diff --git a/components/bootloader_support/test/test_verify_image.c b/components/bootloader_support/test/test_verify_image.c index 0c6d1ac5bfa..a7c37949222 100644 --- a/components/bootloader_support/test/test_verify_image.c +++ b/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 @@ -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); } From caaa29c676d5f69d87191cb4bba9d4a2a83d9d76 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 27 Jun 2017 17:25:44 +1000 Subject: [PATCH 4/7] esp_image_format: ESP32 additional header does not contain an encrypt flag --- components/bootloader_support/include/esp_image_format.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/bootloader_support/include/esp_image_format.h b/components/bootloader_support/include/esp_image_format.h index 2e49252aa5e..0334e5e40ad 100644 --- a/components/bootloader_support/include/esp_image_format.h +++ b/components/bootloader_support/include/esp_image_format.h @@ -63,8 +63,7 @@ typedef struct { 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) */ uint32_t entry_addr; - uint8_t encrypt_flag; /* encrypt flag */ - uint8_t extra_header[15]; /* ESP32 additional header, unused by second bootloader */ + uint8_t extra_header[16]; /* ESP32 additional header, unused by second bootloader */ } esp_image_header_t; /* Header of binary image segment */ From 17adb40ca8d9159e28c3666cf46085eb16ddc7f6 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 28 Jun 2017 16:46:34 +1000 Subject: [PATCH 5/7] bootloader: Calculate SHA256 hash of image on every boot Makes app image booting more reliable (256-bit rather than 8-bit verification.) Some measurements, time to boot a 655KB app.bin file and run to app_main() execution. (All for rev 1 silicon, ie no 340ms spurious WDT delay.) 80MHz QIO mode: before = 300ms after = 140ms 40MHz DIO mode: before = 712ms after = 577ms 40MHz DIO mode, secure boot enabled before = 1380ms after = 934ms (Secure boot involves two ECC signature verifications (partition table, app) that take approx 300ms each with 80MHz CPU.) --- components/app_update/esp_ota_ops.c | 4 +- .../subproject/main/bootloader_start.c | 35 +-- .../include/esp_image_format.h | 46 ++-- .../include/esp_secure_boot.h | 10 + .../include_priv/bootloader_sha.h | 2 +- .../bootloader_support/src/bootloader_sha.c | 32 ++- .../bootloader_support/src/esp_image_format.c | 214 ++++++++++++++---- .../src/secure_boot_signatures.c | 69 +++--- components/esptool_py/esptool | 2 +- components/soc/esp32/include/soc/dport_reg.h | 2 + components/spi_flash/flash_mmap.c | 8 +- tools/unit-test-app/sdkconfig | 1 + 12 files changed, 289 insertions(+), 136 deletions(-) diff --git a/components/app_update/esp_ota_ops.c b/components/app_update/esp_ota_ops.c index a9247bf51ee..534fbb110a5 100644 --- a/components/app_update/esp_ota_ops.c +++ b/components/app_update/esp_ota_ops.c @@ -241,7 +241,7 @@ esp_err_t esp_ota_end(esp_ota_handle_t handle) } #ifdef CONFIG_SECURE_BOOT_ENABLED - ret = esp_secure_boot_verify_signature(it->part->address, data.image_length); + ret = esp_secure_boot_verify_signature(it->part->address, data.image_len); if (ret != ESP_OK) { ret = ESP_ERR_OTA_VALIDATE_FAILED; goto cleanup; @@ -385,7 +385,7 @@ esp_err_t esp_ota_set_boot_partition(const esp_partition_t *partition) } #ifdef CONFIG_SECURE_BOOT_ENABLED - esp_err_t ret = esp_secure_boot_verify_signature(partition->address, data.image_length); + esp_err_t ret = esp_secure_boot_verify_signature(partition->address, data.image_len); 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 75f481ba274..dd2d6a88d6b 100644 --- a/components/bootloader/subproject/main/bootloader_start.c +++ b/components/bootloader/subproject/main/bootloader_start.c @@ -87,11 +87,15 @@ 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); +#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)); @@ -425,24 +429,13 @@ static void unpack_load_app(const esp_partition_pos_t* partition) esp_image_metadata_t data; /* 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, 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; - } - ESP_LOGD(TAG, "App signature is valid"); - } -#endif - uint32_t drom_addr = 0; uint32_t drom_load_addr = 0; uint32_t drom_size = 0; @@ -497,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 ); diff --git a/components/bootloader_support/include/esp_image_format.h b/components/bootloader_support/include/esp_image_format.h index 0334e5e40ad..25d81d83c7f 100644 --- a/components/bootloader_support/include/esp_image_format.h +++ b/components/bootloader_support/include/esp_image_format.h @@ -59,12 +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 extra_header[16]; /* 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 { @@ -97,18 +112,23 @@ typedef enum { * * If encryption is enabled, data will be transparently decrypted. * + * @param mode Mode of operation (verify, silent verify, or load). * @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. + * @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. * * Image validation checks: - * - Magic byte - * - Partition smaller than 16MB - * - All segments & image fit in partition - * - 8 bit image checksum is valid - * - (Signature) if signature verification is enabled + * - 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 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. + * @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(esp_image_load_mode_t mode, const esp_partition_pos_t *part, esp_image_metadata_t *data); diff --git a/components/bootloader_support/include/esp_secure_boot.h b/components/bootloader_support/include/esp_secure_boot.h index 003328557d3..6aa4b6285e2 100644 --- a/components/bootloader_support/include/esp_secure_boot.h +++ b/components/bootloader_support/include/esp_secure_boot.h @@ -77,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 */ diff --git a/components/bootloader_support/include_priv/bootloader_sha.h b/components/bootloader_support/include_priv/bootloader_sha.h index 0434000d080..38bd080485d 100644 --- a/components/bootloader_support/include_priv/bootloader_sha.h +++ b/components/bootloader_support/include_priv/bootloader_sha.h @@ -1,4 +1,4 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// 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. diff --git a/components/bootloader_support/src/bootloader_sha.c b/components/bootloader_support/src/bootloader_sha.c index dc029d9f26b..079093ef8af 100644 --- a/components/bootloader_support/src/bootloader_sha.c +++ b/components/bootloader_support/src/bootloader_sha.c @@ -1,4 +1,4 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// 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. @@ -43,7 +43,11 @@ void bootloader_sha256_finish(bootloader_sha256_handle_t handle, uint8_t *digest { assert(handle != NULL); mbedtls_sha256_context *ctx = (mbedtls_sha256_context *)handle; - mbedtls_sha256_finish(ctx, digest); + if (digest != NULL) { + mbedtls_sha256_finish(ctx, digest); + } + mbedtls_sha256_free(ctx); + free(handle); } #else // Bootloader version @@ -58,6 +62,8 @@ 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() { @@ -116,22 +122,28 @@ 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; - ets_printf("Padding from %d bytes\n", data_words * 4); - // Pad to a 60 byte long block loaded in the engine - // (normally end of block is a 64-bit length, but we know - // the upper 32 bits will be zeroes.) + // 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 = 60 - block_bytes; + 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 == 56/4); + 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 ); @@ -139,15 +151,13 @@ void bootloader_sha256_finish(bootloader_sha256_handle_t handle, uint8_t *digest assert(words_hashed % BLOCK_WORDS == 0); - ets_printf("Padded to %d bytes\n", words_hashed * 4); - 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 < BLOCK_WORDS; i++) { + for (int i = 0; i < DIGEST_WORDS; i++) { digest_words[i] = __builtin_bswap32(sha_text_reg[i]); } asm volatile ("memw"); diff --git a/components/bootloader_support/src/esp_image_format.c b/components/bootloader_support/src/esp_image_format.c index aac9275a682..2eb31da5297 100644 --- a/components/bootloader_support/src/esp_image_format.c +++ b/components/bootloader_support/src/esp_image_format.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -24,11 +25,13 @@ static const char *TAG = "esp_image"; +#define HASH_LEN 32 /* SHA-256 digest length */ + #define SIXTEEN_MB 0x1000000 #define ESP_ROM_CHECKSUM_INITIAL 0xEF /* Headroom to ensure between stack SP (at time of checking) and data loaded from flash */ -#define STACK_LOAD_HEADROOM 4096 +#define STACK_LOAD_HEADROOM 32768 #ifdef BOOTLOADER_BUILD /* 64 bits of random data to obfuscate loaded RAM with, until verification is complete @@ -60,6 +63,11 @@ static esp_err_t verify_segment_header(int index, const esp_image_segment_header } \ 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) { #ifdef BOOTLOADER_BUILD @@ -72,7 +80,6 @@ esp_err_t esp_image_load(esp_image_load_mode_t mode, const esp_partition_pos_t * // 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; - uint8_t image_digest[32] = { 0 }; if (data == NULL || part == NULL) { return ESP_ERR_INVALID_ARG; @@ -86,17 +93,24 @@ esp_err_t esp_image_load(esp_image_load_mode_t mode, const esp_partition_pos_t * bzero(data, sizeof(esp_image_metadata_t)); data->start_addr = part->offset; - sha_handle = bootloader_sha256_start(); - if (sha_handle == NULL) { - return ESP_ERR_NO_MEM; - } - 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; } - bootloader_sha256_data(sha_handle, &data->image, sizeof(esp_image_header_t)); + + // 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; + } + 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, @@ -134,48 +148,32 @@ goto err; FAIL_LOAD("image offset has wrapped"); } - uint32_t unpadded_length = end_addr - data->start_addr; - uint32_t length = unpadded_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); + 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) { + goto err; } - // Verify checksum - uint32_t buf[16/sizeof(uint32_t)]; - err = bootloader_flash_read(end_addr, buf, length - unpadded_length, true); - uint8_t calc = ((uint8_t *)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) { - FAIL_LOAD("Checksum failed. Calculated 0x%x read 0x%x", - checksum, calc); + if (data->image_len > part->size) { + FAIL_LOAD("Image length %d doesn't fit in partition length %d", data->image_len, part->size); } - bootloader_sha256_data(sha_handle, buf, length - unpadded_length); - bootloader_sha256_finish(sha_handle, image_digest); - -#if BOOT_LOG_LEVEL >= LOG_LEVEL_DEBUG - char digest_print[sizeof(image_digest)*2 + 1]; - digest_print[sizeof(image_digest)*2] = 0; - for (int i = 0; i < sizeof(image_digest); i++) { - for (int shift = 0; shift < 2; shift++) { - uint8_t nibble = (image_digest[i] >> (shift ? 0 : 4)) & 0x0F; - if (nibble < 10) { - digest_print[i*2+shift] = '0' + nibble; - } else { - digest_print[i*2+shift] = 'a' + nibble - 10; - } +#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; } } - ESP_LOGD(TAG, "Total image length %d bytes (unpagged %d)", length, unpadded_length); - ESP_LOGD(TAG, "Image SHA256 digest: %s", digest_print); #endif - // Verify digest here - - data->image_length = length; #ifdef BOOTLOADER_BUILD if (do_load) { // Need to deobfuscate RAM @@ -199,8 +197,8 @@ goto err; err = ESP_ERR_IMAGE_INVALID; } if (sha_handle != NULL) { - // Need to finish the digest process to free the handle - bootloader_sha256_finish(sha_handle, image_digest); + // 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)); @@ -238,9 +236,12 @@ static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segme /* 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; } - bootloader_sha256_data(sha_handle, header, sizeof(esp_image_segment_header_t)); + if (sha_handle != NULL) { + bootloader_sha256_data(sha_handle, header, sizeof(esp_image_segment_header_t)); + } intptr_t load_addr = header->load_addr; uint32_t data_len = header->data_len; @@ -311,7 +312,7 @@ static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segme // counter-intuitive, but it's ~3ms better than using the // SHA256 block size. const size_t SHA_CHUNK = 1024; - if (i % SHA_CHUNK == 0) { + if (sha_handle != NULL && i % SHA_CHUNK == 0) { bootloader_sha256_data(sha_handle, &src[w_i], MIN(SHA_CHUNK, data_len - i)); } @@ -349,7 +350,7 @@ static esp_err_t verify_segment_header(int index, const esp_image_segment_header 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 has load address 0x%08x, doesn't match segment data at 0x%08x", + 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; @@ -411,3 +412,120 @@ esp_err_t esp_image_verify_bootloader(uint32_t *length) } 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); + + // 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; + } + + 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 +} diff --git a/components/bootloader_support/src/secure_boot_signatures.c b/components/bootloader_support/src/secure_boot_signatures.c index 3ca2f2e9197..988ab7935f2 100644 --- a/components/bootloader_support/src/secure_boot_signatures.c +++ b/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; } diff --git a/components/esptool_py/esptool b/components/esptool_py/esptool index 325f01637b6..a4207741eca 160000 --- a/components/esptool_py/esptool +++ b/components/esptool_py/esptool @@ -1 +1 @@ -Subproject commit 325f01637b667af02cc6390965b09d50e6a31dac +Subproject commit a4207741eca1fb8e5e3670e498ed058320bbcb5a diff --git a/components/soc/esp32/include/soc/dport_reg.h b/components/soc/esp32/include/soc/dport_reg.h index 4e17363bcfe..852967f1c19 100644 --- a/components/soc/esp32/include/soc/dport_reg.h +++ b/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_ */ diff --git a/components/spi_flash/flash_mmap.c b/components/spi_flash/flash_mmap.c index 080532f8622..2db0c2437f9 100644 --- a/components/spi_flash/flash_mmap.c +++ b/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; } } } diff --git a/tools/unit-test-app/sdkconfig b/tools/unit-test-app/sdkconfig index c3500943cf0..29be2173ddd 100644 --- a/tools/unit-test-app/sdkconfig +++ b/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 From d1b66a08c1d9838b139e9fef6cc2d38e5d5ee57d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 30 Jun 2017 17:14:50 +1000 Subject: [PATCH 6/7] bootloader: Add option to build with Link Time Optimisation enabled --- components/bootloader/Kconfig.projbuild | 13 +++++++++++-- .../bootloader/subproject/main/Makefile.projbuild | 5 +++++ make/project.mk | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/components/bootloader/Kconfig.projbuild b/components/bootloader/Kconfig.projbuild index 5af223c0ad4..b4f2c904ac5 100644 --- a/components/bootloader/Kconfig.projbuild +++ b/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. diff --git a/components/bootloader/subproject/main/Makefile.projbuild b/components/bootloader/subproject/main/Makefile.projbuild index c368c68416c..afd5282d83b 100644 --- a/components/bootloader/subproject/main/Makefile.projbuild +++ b/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 diff --git a/make/project.mk b/make/project.mk index cc439a3582e..159a2616741 100644 --- a/make/project.mk +++ b/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 From 871ba41a0972ccd9e619192ab1dc5c720cb3d772 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Mon, 17 Jul 2017 14:37:06 +1000 Subject: [PATCH 7/7] bootloader: Remove unnecessary KEEP on data/bss sections, save static RAM --- .../subproject/main/esp32.bootloader.ld | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/components/bootloader/subproject/main/esp32.bootloader.ld b/components/bootloader/subproject/main/esp32.bootloader.ld index 3f1b9eb0b62..54fe1a9a3c0 100644 --- a/components/bootloader/subproject/main/esp32.bootloader.ld +++ b/components/bootloader/subproject/main/esp32.bootloader.ld @@ -47,7 +47,7 @@ SECTIONS *(.sbss2.*) *(.gnu.linkonce.sb2.*) *(.dynbss) - KEEP(*(.bss)) + *(.bss) *(.bss.*) *(.gnu.linkonce.b.*) *(COMMON) @@ -58,17 +58,17 @@ SECTIONS .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