diff --git a/components/bootloader/subproject/Makefile b/components/bootloader/subproject/Makefile index edf34e8d..ebd028c6 100644 --- a/components/bootloader/subproject/Makefile +++ b/components/bootloader/subproject/Makefile @@ -8,7 +8,7 @@ endif PROJECT_NAME := bootloader -COMPONENTS := esptool_py main bootloader_support spi_flash log esp8266 +COMPONENTS := esptool_py main bootloader_support spi_flash log esp8266 util # Clear C and CXX from top level project CFLAGS = diff --git a/components/bootloader/subproject/main/bootloader_start.c b/components/bootloader/subproject/main/bootloader_start.c index e8930ab4..75d3744f 100644 --- a/components/bootloader/subproject/main/bootloader_start.c +++ b/components/bootloader/subproject/main/bootloader_start.c @@ -14,8 +14,9 @@ #include "sdkconfig.h" -#include "bootloader_init.h" #include "bootloader_config.h" +#include "bootloader_init.h" + #include "esp_image_format.h" #include "esp_log.h" @@ -38,12 +39,30 @@ void call_start_cpu(void) } // 3. Loading the selected image -// bootloader_utility_load_image(&image_data); + bootloader_utility_load_image(&image_data); } // Selects image to boot static esp_err_t select_image (esp_image_metadata_t *image_data) { + // 1. Load partition table + bootloader_state_t bs;// = { 0 }; + memset(&bs, 0, sizeof(bootloader_state_t)); + if (!bootloader_utility_load_partition_table(&bs)) { + ESP_LOGE(TAG, "load partition table error!"); + return ESP_FAIL; + } + + // 2. Select boot partition + int boot_index = selected_boot_partition(&bs); + if(boot_index == INVALID_INDEX) { + return ESP_FAIL; // Unrecoverable failure (not due to corrupt ota data or bad partition contents) + } + + // 3. Load the app image for booting + if (!bootloader_utility_load_boot_image(&bs, boot_index, image_data)) { + return ESP_FAIL; + } return ESP_OK; } @@ -53,7 +72,43 @@ static esp_err_t select_image (esp_image_metadata_t *image_data) */ static int selected_boot_partition(const bootloader_state_t *bs) { - int boot_index = 1; //bootloader_utility_get_selected_boot_partition(bs); - + int boot_index = bootloader_utility_get_selected_boot_partition(bs); + if (boot_index == INVALID_INDEX) { + return boot_index; // Unrecoverable failure (not due to corrupt ota data or bad partition contents) + } else { + // Factory firmware. +#ifdef CONFIG_BOOTLOADER_FACTORY_RESET + if (bootloader_common_check_long_hold_gpio(CONFIG_BOOTLOADER_NUM_PIN_FACTORY_RESET, CONFIG_BOOTLOADER_HOLD_TIME_GPIO) == 1) { + ESP_LOGI(TAG, "Detect a condition of the factory reset"); + bool ota_data_erase = false; +#ifdef CONFIG_BOOTLOADER_OTA_DATA_ERASE + ota_data_erase = true; +#endif + const char *list_erase = CONFIG_BOOTLOADER_DATA_FACTORY_RESET; + ESP_LOGI(TAG, "Data partitions to erase: %s", list_erase); + if (bootloader_common_erase_part_type_data(list_erase, ota_data_erase) == false) { + ESP_LOGE(TAG, "Not all partitions were erased"); + } + return bootloader_utility_get_selected_boot_partition(bs); + } +#endif + // TEST firmware. +#ifdef CONFIG_BOOTLOADER_APP_TEST + if (bootloader_common_check_long_hold_gpio(CONFIG_BOOTLOADER_NUM_PIN_APP_TEST, CONFIG_BOOTLOADER_HOLD_TIME_GPIO) == 1) { + ESP_LOGI(TAG, "Detect a boot condition of the test firmware"); + if (bs->test.offset != 0) { + boot_index = TEST_APP_INDEX; + return boot_index; + } else { + ESP_LOGE(TAG, "Test firmware is not found in partition table"); + return INVALID_INDEX; + } + } +#endif + // Customer implementation. + // if (gpio_pin_1 == true && ...){ + // boot_index = required_boot_partition; + // } ... + } return boot_index; } diff --git a/components/bootloader/subproject/main/esp8266.bootloader.ld b/components/bootloader/subproject/main/esp8266.bootloader.ld index 7476d839..cacb39c9 100644 --- a/components/bootloader/subproject/main/esp8266.bootloader.ld +++ b/components/bootloader/subproject/main/esp8266.bootloader.ld @@ -16,7 +16,7 @@ MEMORY dram_seg : org = 0x3FFE8000, len = 0x18000 /* Functions which are critical should be put in this segment. */ - iram_seg : org = 0x40108000, len = 0x8000 + iram_seg : org = 0x40108000, len = 0x4000 } /* Default entry point: */ diff --git a/components/bootloader_support/include/esp_secure_boot.h b/components/bootloader_support/include/esp_secure_boot.h index 6aa4b628..35dc8e9e 100644 --- a/components/bootloader_support/include/esp_secure_boot.h +++ b/components/bootloader_support/include/esp_secure_boot.h @@ -15,7 +15,7 @@ #include #include -#include "soc/efuse_reg.h" +//#include "soc/efuse_reg.h" #ifdef __cplusplus extern "C" { @@ -34,9 +34,9 @@ extern "C" { * * @return true if secure boot is enabled. */ -static inline bool esp_secure_boot_enabled(void) { - return REG_READ(EFUSE_BLK0_RDATA6_REG) & EFUSE_RD_ABS_DONE_0; -} +//static inline bool esp_secure_boot_enabled(void) { +// return REG_READ(EFUSE_BLK0_RDATA6_REG) & EFUSE_RD_ABS_DONE_0; +//} /** @brief Enable secure boot if it is not already enabled. diff --git a/components/bootloader_support/src/bootloader_common.c b/components/bootloader_support/src/bootloader_common.c index 773089a4..c85085f8 100644 --- a/components/bootloader_support/src/bootloader_common.c +++ b/components/bootloader_support/src/bootloader_common.c @@ -159,3 +159,27 @@ bool bootloader_common_erase_part_type_data(const char *list_erase, bool ota_dat } #endif + +#ifdef CONFIG_TARGET_PLATFORM_ESP8266 + +#include +#include + +#include "esp_err.h" +#include "esp_log.h" + +#include "bootloader_config.h" + +static const char* TAG = "boot_comm"; + +uint32_t bootloader_common_ota_select_crc(const esp_ota_select_entry_t *s) +{ + return crc32_le(UINT32_MAX, (uint8_t*)&s->ota_seq, 4); +} + +bool bootloader_common_ota_select_valid(const esp_ota_select_entry_t *s) +{ + return s->ota_seq != UINT32_MAX && s->crc == bootloader_common_ota_select_crc(s); +} + +#endif diff --git a/components/bootloader_support/src/bootloader_flash.c b/components/bootloader_support/src/bootloader_flash.c index dfe15e3b..96db4f72 100644 --- a/components/bootloader_support/src/bootloader_flash.c +++ b/components/bootloader_support/src/bootloader_flash.c @@ -274,6 +274,39 @@ SpiFlashOpResult SPIRead(uint32_t addr, void *dst, uint32_t size); SpiFlashOpResult SPIWrite(uint32_t addr, const uint8_t *src, uint32_t size); SpiFlashOpResult SPIEraseSector(uint32_t sector_num); +static bool mapped; + +const void *bootloader_mmap(uint32_t src_addr, uint32_t size) +{ + if (mapped) { + ESP_LOGE(TAG, "tried to bootloader_mmap twice"); + return NULL; /* can't map twice */ + } + + /* ToDo: Improve the map policy! */ + + Cache_Read_Disable(); + + /* */ + if (src_addr < 0x100000) { + Cache_Read_Enable(0, 0, 0); + } else { + Cache_Read_Enable(1, 0, 0); + } + + mapped = true; + + return (void *)(0x40200000 + src_addr); +} + +void bootloader_munmap(const void *mapping) +{ + if (mapped) { + Cache_Read_Disable(); + mapped = false; + } +} + static esp_err_t bootloader_flash_read_no_decrypt(size_t src_addr, void *dest, size_t size) { SPIRead(src_addr, dest, size); diff --git a/components/bootloader_support/src/bootloader_utility.c b/components/bootloader_support/src/bootloader_utility.c index a806d1f8..64632d9a 100644 --- a/components/bootloader_support/src/bootloader_utility.c +++ b/components/bootloader_support/src/bootloader_utility.c @@ -477,3 +477,345 @@ static void set_cache_and_start_app( } #endif + +#ifdef CONFIG_TARGET_PLATFORM_ESP8266 + +#include +#include + +#include "bootloader_config.h" +#include "bootloader_utility.h" + +#include "esp_err.h" +#include "esp_log.h" + +#include "esp_flash_partitions.h" + +static const char* TAG = "boot"; + +bool bootloader_utility_load_partition_table(bootloader_state_t* bs) +{ + const esp_partition_info_t *partitions; + const char *partition_usage; + esp_err_t err; + int num_partitions; + +#ifdef CONFIG_SECURE_BOOT_ENABLED + if(esp_secure_boot_enabled()) { + ESP_LOGI(TAG, "Verifying partition table signature..."); + err = esp_secure_boot_verify_signature(ESP_PARTITION_TABLE_ADDR, ESP_PARTITION_TABLE_MAX_LEN); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to verify partition table signature."); + return false; + } + ESP_LOGD(TAG, "Partition table signature verified"); + } +#endif + + partitions = bootloader_mmap(ESP_PARTITION_TABLE_ADDR, ESP_PARTITION_TABLE_MAX_LEN); + if (!partitions) { + ESP_LOGE(TAG, "bootloader_mmap(0x%x, 0x%x) failed", ESP_PARTITION_TABLE_ADDR, ESP_PARTITION_TABLE_MAX_LEN); + return false; + } + ESP_LOGD(TAG, "mapped partition table 0x%x at 0x%x", ESP_PARTITION_TABLE_ADDR, (intptr_t)partitions); + + err = esp_partition_table_basic_verify(partitions, true, &num_partitions); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to verify partition table"); + return false; + } + + ESP_LOGI(TAG, "Partition Table:"); + ESP_LOGI(TAG, "## Label Usage Type ST Offset Length"); + + for(int i = 0; i < num_partitions; i++) { +// const esp_partition_info_t *partition = &partitions[i]; + esp_partition_info_t partiton_local; + esp_partition_info_t *partition = &partiton_local; + + memcpy(&partiton_local, (intptr_t)partitions + i * sizeof(esp_partition_info_t), sizeof(esp_partition_info_t)); + + ESP_LOGD(TAG, "load partition table entry 0x%x", (intptr_t)partition); + ESP_LOGD(TAG, "type=%x subtype=%x", partition->type, partition->subtype); + partition_usage = "unknown"; + + /* valid partition table */ + switch(partition->type) { + case PART_TYPE_APP: /* app partition */ + switch(partition->subtype) { + case PART_SUBTYPE_FACTORY: /* factory binary */ + bs->factory = partition->pos; + partition_usage = "factory app"; + break; + case PART_SUBTYPE_TEST: /* test binary */ + bs->test = partition->pos; + partition_usage = "test app"; + break; + default: + /* OTA binary */ + if ((partition->subtype & ~PART_SUBTYPE_OTA_MASK) == PART_SUBTYPE_OTA_FLAG) { + bs->ota[partition->subtype & PART_SUBTYPE_OTA_MASK] = partition->pos; + ++bs->app_count; + partition_usage = "OTA app"; + } + else { + partition_usage = "Unknown app"; + } + break; + } + break; /* PART_TYPE_APP */ + case PART_TYPE_DATA: /* data partition */ + switch(partition->subtype) { + case PART_SUBTYPE_DATA_OTA: /* ota data */ + bs->ota_info = partition->pos; + partition_usage = "OTA data"; + break; + case PART_SUBTYPE_DATA_RF: + partition_usage = "RF data"; + break; + case PART_SUBTYPE_DATA_WIFI: + partition_usage = "WiFi data"; + break; + default: + partition_usage = "Unknown data"; + break; + } + break; /* PARTITION_USAGE_DATA */ + default: /* other partition type */ + break; + } + + /* print partition type info */ + ESP_LOGI(TAG, "%2d %-16s %-16s %02x %02x %08x %08x", i, partition->label, partition_usage, + partition->type, partition->subtype, + partition->pos.offset, partition->pos.size); + } + + bootloader_munmap(partitions); + + ESP_LOGI(TAG,"End of partition table"); + return true; +} + +int bootloader_utility_get_selected_boot_partition(const bootloader_state_t *bs) +{ + esp_ota_select_entry_t sa,sb; + const esp_ota_select_entry_t *ota_select_map; + + if (bs->ota_info.offset != 0) { + // partition table has OTA data partition + if (bs->ota_info.size < 2 * SPI_SEC_SIZE) { + ESP_LOGE(TAG, "ota_info partition size %d is too small (minimum %d bytes)", bs->ota_info.size, sizeof(esp_ota_select_entry_t)); + return INVALID_INDEX; // can't proceed + } + + ESP_LOGD(TAG, "OTA data offset 0x%x", bs->ota_info.offset); + ota_select_map = bootloader_mmap(bs->ota_info.offset, bs->ota_info.size); + if (!ota_select_map) { + ESP_LOGE(TAG, "bootloader_mmap(0x%x, 0x%x) failed", bs->ota_info.offset, bs->ota_info.size); + return INVALID_INDEX; // can't proceed + } + memcpy(&sa, ota_select_map, sizeof(esp_ota_select_entry_t)); + memcpy(&sb, (uint8_t *)ota_select_map + SPI_SEC_SIZE, sizeof(esp_ota_select_entry_t)); + bootloader_munmap(ota_select_map); + + ESP_LOGD(TAG, "OTA sequence values A 0x%08x B 0x%08x", sa.ota_seq, sb.ota_seq); + if(sa.ota_seq == UINT32_MAX && sb.ota_seq == UINT32_MAX) { + ESP_LOGD(TAG, "OTA sequence numbers both empty (all-0xFF)"); + if (bs->factory.offset != 0) { + ESP_LOGI(TAG, "Defaulting to factory image"); + return FACTORY_INDEX; + } else { + ESP_LOGI(TAG, "No factory image, trying OTA 0"); + return 0; + } + } else { + bool ota_valid = false; + const char *ota_msg; + int ota_seq; // Raw OTA sequence number. May be more than # of OTA slots + if(bootloader_common_ota_select_valid(&sa) && bootloader_common_ota_select_valid(&sb)) { + ota_valid = true; + ota_msg = "Both OTA values"; + ota_seq = MAX(sa.ota_seq, sb.ota_seq) - 1; + } else if(bootloader_common_ota_select_valid(&sa)) { + ota_valid = true; + ota_msg = "Only OTA sequence A is"; + ota_seq = sa.ota_seq - 1; + } else if(bootloader_common_ota_select_valid(&sb)) { + ota_valid = true; + ota_msg = "Only OTA sequence B is"; + ota_seq = sb.ota_seq - 1; + } + + if (ota_valid) { + int ota_slot = ota_seq % bs->app_count; // Actual OTA partition selection + ESP_LOGD(TAG, "%s valid. Mapping seq %d -> OTA slot %d", ota_msg, ota_seq, ota_slot); + return ota_slot; + } else if (bs->factory.offset != 0) { + ESP_LOGE(TAG, "ota data partition invalid, falling back to factory"); + return FACTORY_INDEX; + } else { + ESP_LOGE(TAG, "ota data partition invalid and no factory, will try all partitions"); + return FACTORY_INDEX; + } + } + } + + // otherwise, start from factory app partition and let the search logic + // proceed from there + return FACTORY_INDEX; +} + +/* Given a partition index, return the partition position data from the bootloader_state_t structure */ +static esp_partition_pos_t index_to_partition(const bootloader_state_t *bs, int index) +{ + if (index == FACTORY_INDEX) { + return bs->factory; + } + + if (index == TEST_APP_INDEX) { + return bs->test; + } + + if (index >= 0 && index < MAX_OTA_SLOTS && index < bs->app_count) { + return bs->ota[index]; + } + + esp_partition_pos_t invalid = { 0 }; + return invalid; +} + +static void log_invalid_app_partition(int index) +{ + const char *not_bootable = " is not bootable"; /* save a few string literal bytes */ + switch(index) { + case FACTORY_INDEX: + ESP_LOGE(TAG, "Factory app partition%s", not_bootable); + break; + case TEST_APP_INDEX: + ESP_LOGE(TAG, "Factory test app partition%s", not_bootable); + break; + default: + ESP_LOGE(TAG, "OTA app partition slot %d%s", index, not_bootable); + break; + } +} + +/* Return true if a partition has a valid app image that was successfully loaded */ +static bool try_load_partition(const esp_partition_pos_t *partition, esp_image_metadata_t *data) +{ + if (partition->size == 0) { + ESP_LOGD(TAG, "Can't boot from zero-length partition"); + return false; + } +#ifdef BOOTLOADER_BUILD + if (esp_image_load(ESP_IMAGE_LOAD, partition, data) == ESP_OK) { + ESP_LOGI(TAG, "Loaded app from partition at offset 0x%x", + partition->offset); + return true; + } +#endif + + return false; +} + +#define TRY_LOG_FORMAT "Trying partition index %d offs 0x%x size 0x%x" + +bool bootloader_utility_load_boot_image(const bootloader_state_t *bs, int start_index, esp_image_metadata_t *result) +{ + int index = start_index; + esp_partition_pos_t part; + if(start_index == TEST_APP_INDEX) { + if (try_load_partition(&bs->test, result)) { + return true; + } else { + ESP_LOGE(TAG, "No bootable test partition in the partition table"); + return false; + } + } + /* work backwards from start_index, down to the factory app */ + for(index = start_index; index >= FACTORY_INDEX; index--) { + part = index_to_partition(bs, index); + if (part.size == 0) { + continue; + } + ESP_LOGD(TAG, TRY_LOG_FORMAT, index, part.offset, part.size); + if (try_load_partition(&part, result)) { + return true; + } + log_invalid_app_partition(index); + } + + /* failing that work forwards from start_index, try valid OTA slots */ + for(index = start_index + 1; index < bs->app_count; index++) { + part = index_to_partition(bs, index); + if (part.size == 0) { + continue; + } + ESP_LOGD(TAG, TRY_LOG_FORMAT, index, part.offset, part.size); + if (try_load_partition(&part, result)) { + return true; + } + log_invalid_app_partition(index); + } + + if (try_load_partition(&bs->test, result)) { + ESP_LOGW(TAG, "Falling back to test app as only bootable partition"); + return true; + } + + ESP_LOGE(TAG, "No bootable app partitions in the partition table"); + bzero(result, sizeof(esp_image_metadata_t)); + return false; +} + +void bootloader_utility_load_image(const esp_image_metadata_t* image_data) +{ +#if defined(CONFIG_SECURE_BOOT_ENABLED) || defined(CONFIG_FLASH_ENCRYPTION_ENABLED) + esp_err_t err; +#endif +#ifdef CONFIG_SECURE_BOOT_ENABLED + /* Generate secure digest from this bootloader to protect future + modifications */ + ESP_LOGI(TAG, "Checking secure boot..."); + err = esp_secure_boot_permanently_enable(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Bootloader digest generation failed (%d). SECURE BOOT IS NOT ENABLED.", err); + /* Allow booting to continue, as the failure is probably + due to user-configured EFUSEs for testing... + */ + } +#endif + +#ifdef CONFIG_FLASH_ENCRYPTION_ENABLED + /* encrypt flash */ + ESP_LOGI(TAG, "Checking flash encryption..."); + bool flash_encryption_enabled = esp_flash_encryption_enabled(); + err = esp_flash_encrypt_check_and_update(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Flash encryption check failed (%d).", err); + return; + } + + if (!flash_encryption_enabled && esp_flash_encryption_enabled()) { + /* Flash encryption was just enabled for the first time, + so issue a system reset to ensure flash encryption + cache resets properly */ + ESP_LOGI(TAG, "Resetting with flash encryption enabled..."); + REG_WRITE(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_SW_SYS_RST); + return; + } +#endif + +// ESP_LOGI(TAG, "Disabling RNG early entropy source..."); +// bootloader_random_disable(); + + // copy loaded segments to RAM, set up caches for mapped segments, and start application +// unpack_load_app(image_data); + + Cache_Read_Enable(0, 0, 0); + + // ToDo: jump to application code. +} + +#endif diff --git a/components/bootloader_support/src/esp_image_format.c b/components/bootloader_support/src/esp_image_format.c index c5345c49..bf4f91dd 100644 --- a/components/bootloader_support/src/esp_image_format.c +++ b/components/bootloader_support/src/esp_image_format.c @@ -578,3 +578,546 @@ static void debug_log_hash(const uint8_t *image_hash, const char *label) } #endif + +#ifdef CONFIG_TARGET_PLATFORM_ESP8266 + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +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 32768 + +/* Mmap source address mask */ +#define MMAP_ALIGNED_MASK 0x0000FFFF + +#ifdef BOOTLOADER_BUILD +/* 64 bits of random data to obfuscate loaded RAM with, until verification is complete + (Means loaded code isn't executable until after the secure boot check.) +*/ +static uint32_t ram_obfs_value[2]; +#endif + +/* Return true if load_addr is an address the bootloader should load into */ +static bool should_load(uint32_t load_addr); +/* Return true if load_addr is an address the bootloader should map via flash cache */ +static bool should_map(uint32_t load_addr); + +/* Load or verify a segment */ +static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segment_header_t *header, bool silent, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum); + +/* split segment and verify if data_len is too long */ +static esp_err_t process_segment_data(intptr_t load_addr, uint32_t data_addr, uint32_t data_len, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum); + +/* Verify the main image header */ +static esp_err_t verify_image_header(uint32_t src_addr, const esp_image_header_t *image, bool silent); + +/* Verify a segment header */ +static esp_err_t verify_segment_header(int index, const esp_image_segment_header_t *segment, uint32_t segment_data_offs, bool silent); + +/* Log-and-fail macro for use in esp_image_load */ +#define FAIL_LOAD(...) do { \ + if (!silent) { \ + ESP_LOGE(TAG, __VA_ARGS__); \ + } \ + goto err; \ + } \ + while(0) + +static esp_err_t verify_checksum(bootloader_sha256_handle_t sha_handle, uint32_t checksum_word, esp_image_metadata_t *data); + +static esp_err_t __attribute__((unused)) verify_secure_boot_signature(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 + bool do_load = (mode == ESP_IMAGE_LOAD); +#else + bool do_load = false; // Can't load the image in app mode +#endif + bool silent = (mode == ESP_IMAGE_VERIFY_SILENT); + esp_err_t err = ESP_OK; + // checksum the image a word at a time. This shaves 30-40ms per MB of image size + uint32_t checksum_word = ESP_ROM_CHECKSUM_INITIAL; + bootloader_sha256_handle_t sha_handle = NULL; + + if (data == NULL || part == NULL) { + return ESP_ERR_INVALID_ARG; + } + + if (part->size > SIXTEEN_MB) { + err = ESP_ERR_INVALID_ARG; + FAIL_LOAD("partition size 0x%x invalid, larger than 16MB", part->size); + } + + bzero(data, sizeof(esp_image_metadata_t)); + data->start_addr = part->offset; + + ESP_LOGD(TAG, "reading image header @ 0x%x", data->start_addr); + err = bootloader_flash_read(data->start_addr, &data->image, sizeof(esp_image_header_t), true); + if (err != ESP_OK) { + goto err; + } + + // Calculate SHA-256 of image if secure boot is on, or if image has a hash appended +#ifdef CONFIG_SECURE_BOOT_ENABLED + if (1) { +#else +// if (data->image.hash_appended) { +#endif +// sha_handle = bootloader_sha256_start(); +// if (sha_handle == NULL) { +// return ESP_ERR_NO_MEM; +// } +// bootloader_sha256_data(sha_handle, &data->image, sizeof(esp_image_header_t)); +// } + + ESP_LOGD(TAG, "image header: 0x%02x 0x%02x 0x%02x 0x%02x %08x", + data->image.magic, + data->image.segment_count, + data->image.spi_mode, + data->image.spi_size, + data->image.entry_addr); + + err = verify_image_header(data->start_addr, &data->image, silent); + if (err != ESP_OK) { +goto err; + } + + if (data->image.segment_count > ESP_IMAGE_MAX_SEGMENTS) { + FAIL_LOAD("image at 0x%x segment count %d exceeds max %d", + data->start_addr, data->image.segment_count, ESP_IMAGE_MAX_SEGMENTS); + } + + uint32_t next_addr = data->start_addr + sizeof(esp_image_header_t); + + for(int i = 0; i < data->image.segment_count; i++) { + esp_image_segment_header_t *header = &data->segments[i]; + ESP_LOGV(TAG, "loading segment header %d at offset 0x%x", i, next_addr); + err = process_segment(i, next_addr, header, silent, do_load, sha_handle, &checksum_word); + if (err != ESP_OK) { + goto err; + } + next_addr += sizeof(esp_image_segment_header_t); + data->segment_data[i] = next_addr; + next_addr += header->data_len; + } + + // Segments all loaded, verify length + uint32_t end_addr = next_addr; + if (end_addr < data->start_addr) { + FAIL_LOAD("image offset has wrapped"); + } + + data->image_len = end_addr - data->start_addr; + ESP_LOGV(TAG, "image start 0x%08x end of last section 0x%08x", data->start_addr, end_addr); +// if (!esp_cpu_in_ocd_debug_mode()) { + err = verify_checksum(sha_handle, checksum_word, data); + if (err != ESP_OK) { + goto err; + } +// } + if (data->image_len > part->size) { + FAIL_LOAD("Image length %d doesn't fit in partition length %d", data->image_len, part->size); + } + + bool is_bootloader = (data->start_addr == ESP_BOOTLOADER_OFFSET); + /* For secure boot, we don't verify signature on bootloaders. + + For non-secure boot, we don't verify any SHA-256 hash appended to the bootloader because esptool.py may have + rewritten the header - rely on esptool.py having verified the bootloader at flashing time, instead. + */ + if (!is_bootloader) { +#ifdef CONFIG_SECURE_BOOT_ENABLED + // secure boot images have a signature appended + err = verify_secure_boot_signature(sha_handle, data); +#else + // No secure boot, but SHA-256 can be appended for basic corruption detection + if (sha_handle != NULL && !esp_cpu_in_ocd_debug_mode()) { + err = verify_simple_hash(sha_handle, data); + } +#endif // CONFIG_SECURE_BOOT_ENABLED + } else { // is_bootloader + // bootloader may still have a sha256 digest handle open + if (sha_handle != NULL) { + bootloader_sha256_finish(sha_handle, NULL); + } + } + sha_handle = NULL; + if (err != ESP_OK) { + goto err; + } + +#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; + + err: + if (err == ESP_OK) { + err = ESP_ERR_IMAGE_INVALID; + } + if (sha_handle != NULL) { + // Need to finish the hash process to free the handle + bootloader_sha256_finish(sha_handle, NULL); + } + // Prevent invalid/incomplete data leaking out + bzero(data, sizeof(esp_image_metadata_t)); + return err; +} + +static esp_err_t verify_image_header(uint32_t src_addr, const esp_image_header_t *image, bool silent) +{ + esp_err_t err = ESP_OK; + + if (image->magic != ESP_IMAGE_HEADER_MAGIC) { + if (!silent) { + ESP_LOGE(TAG, "image at 0x%x has invalid magic byte", src_addr); + } + err = ESP_ERR_IMAGE_INVALID; + } + if (!silent) { + if (image->spi_mode > ESP_IMAGE_SPI_MODE_SLOW_READ) { + ESP_LOGW(TAG, "image at 0x%x has invalid SPI mode %d", src_addr, image->spi_mode); + } + if (image->spi_speed > ESP_IMAGE_SPI_SPEED_80M) { + ESP_LOGW(TAG, "image at 0x%x has invalid SPI speed %d", src_addr, image->spi_speed); + } + if (image->spi_size > ESP_IMAGE_FLASH_SIZE_MAX) { + ESP_LOGW(TAG, "image at 0x%x has invalid SPI size %d", src_addr, image->spi_size); + } + } + return err; +} + +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; + + /* read segment header */ + memset(header, 0, sizeof(esp_image_segment_header_t)); + err = bootloader_flash_read(flash_addr, header, sizeof(esp_image_segment_header_t), true); + if (err != ESP_OK) { + ESP_LOGE(TAG, "bootloader_flash_read failed at 0x%08x", flash_addr); + return err; + } + if (sha_handle != NULL) { +// bootloader_sha256_data(sha_handle, header, sizeof(esp_image_segment_header_t)); + } + + intptr_t load_addr = header->load_addr; + uint32_t data_len = header->data_len; + uint32_t data_addr = flash_addr + sizeof(esp_image_segment_header_t); + + ESP_LOGV(TAG, "segment data length 0x%x data starts 0x%x", data_len, data_addr); + + err = verify_segment_header(index, header, data_addr, silent); + if (err != ESP_OK) { + return err; + } + + if (data_len % 4 != 0) { + FAIL_LOAD("unaligned segment length 0x%x", data_len); + } + + bool is_mapping = should_map(load_addr); + do_load = do_load && should_load(load_addr); + + if (!silent) { + ESP_LOGI(TAG, "segment %d: paddr=0x%08x vaddr=0x%08x size=0x%05x (%6d) %s", + index, data_addr, load_addr, + data_len, data_len, + (do_load)?"load":(is_mapping)?"map":""); + } + + if (do_load) { + /* Before loading segment, check it doesn't clobber bootloader RAM... */ + uint32_t end_addr = load_addr + data_len; + if (end_addr < 0x40000000) { +// intptr_t sp = (intptr_t)get_sp(); +// if (end_addr > sp - STACK_LOAD_HEADROOM) { +// ESP_LOGE(TAG, "Segment %d end address 0x%08x too high (bootloader stack 0x%08x liimit 0x%08x)", +// index, end_addr, sp, sp - STACK_LOAD_HEADROOM); +// return ESP_ERR_IMAGE_INVALID; +// } + } + } +#ifndef BOOTLOADER_BUILD + uint32_t free_page_count = spi_flash_mmap_get_free_pages(SPI_FLASH_MMAP_DATA); + ESP_LOGD(TAG, "free data page_count 0x%08x",free_page_count); + uint32_t offset_page = 0; + while (data_len >= free_page_count * SPI_FLASH_MMU_PAGE_SIZE) { + offset_page = ((data_addr & MMAP_ALIGNED_MASK) != 0)?1:0; + err = process_segment_data(load_addr, data_addr, (free_page_count - offset_page) * SPI_FLASH_MMU_PAGE_SIZE, do_load, sha_handle, checksum); + if (err != ESP_OK) { + return err; + } + data_addr += (free_page_count - offset_page) * SPI_FLASH_MMU_PAGE_SIZE; + data_len -= (free_page_count - offset_page) * SPI_FLASH_MMU_PAGE_SIZE; + } +#endif + err = process_segment_data(load_addr, data_addr, data_len, do_load, sha_handle, checksum); + if (err != ESP_OK) { + return err; + } + return ESP_OK; + +err: + if (err == ESP_OK) { + err = ESP_ERR_IMAGE_INVALID; + } + + return err; +} + +static esp_err_t process_segment_data(intptr_t load_addr, uint32_t data_addr, uint32_t data_len, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum) +{ + const uint32_t *data = (const uint32_t *)bootloader_mmap(data_addr, data_len); + if(!data) { + ESP_LOGE(TAG, "bootloader_mmap(0x%x, 0x%x) failed", + data_addr, data_len); + return ESP_FAIL; + } + +#ifdef BOOTLOADER_BUILD + // Set up the obfuscation value to use for loading +// while (ram_obfs_value[0] == 0 || ram_obfs_value[1] == 0) { +// bootloader_fill_random(ram_obfs_value, sizeof(ram_obfs_value)); +// } + ram_obfs_value[0] = 0x55; + ram_obfs_value[1] = 0xaa; + uint32_t *dest = (uint32_t *)load_addr; +#endif + + const uint32_t *src = data; + + for (int i = 0; i < data_len; i += 4) { + int w_i = i/4; // Word index + uint32_t w = src[w_i]; + *checksum ^= w; +#ifdef BOOTLOADER_BUILD + if (do_load) { +// dest[w_i] = w ^ ((w_i & 1) ? ram_obfs_value[0] : ram_obfs_value[1]); + } +#endif + // SHA_CHUNK determined experimentally as the optimum size + // to call bootloader_sha256_data() with. This is a bit + // counter-intuitive, but it's ~3ms better than using the + // SHA256 block size. + const size_t SHA_CHUNK = 1024; + if (sha_handle != NULL && i % SHA_CHUNK == 0) { +// bootloader_sha256_data(sha_handle, &src[w_i], +// MIN(SHA_CHUNK, data_len - i)); + } + } + + bootloader_munmap(data); + + return ESP_OK; +} + +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; +// } +// +// 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 (map_segment +// && ((segment_data_offs % SPI_FLASH_MMU_PAGE_SIZE) != (load_addr % SPI_FLASH_MMU_PAGE_SIZE))) { +// if (!silent) { +// ESP_LOGE(TAG, "Segment %d load address 0x%08x, doesn't match data 0x%08x", +// index, load_addr, segment_data_offs); +// } +// return ESP_ERR_IMAGE_INVALID; +// } + + return ESP_OK; +} + +static bool should_map(uint32_t load_addr) +{ + return (load_addr >= 0x40200000 && load_addr < 0x40300000); +} + +static bool should_load(uint32_t load_addr) +{ + if (should_map(load_addr)) { + return false; + } + + return true; +} + +esp_err_t esp_image_verify_bootloader(uint32_t *length) +{ + esp_image_metadata_t data; + const esp_partition_pos_t bootloader_part = { + .offset = ESP_BOOTLOADER_OFFSET, + .size = ESP_PARTITION_TABLE_OFFSET - ESP_BOOTLOADER_OFFSET, + }; + esp_err_t err = esp_image_load(ESP_IMAGE_VERIFY, + &bootloader_part, + &data); + if (length != NULL) { + *length = (err == ESP_OK) ? data.image_len : 0; + } + return err; +} + +static esp_err_t verify_checksum(bootloader_sha256_handle_t sha_handle, uint32_t checksum_word, esp_image_metadata_t *data) +{ + uint32_t unpadded_length = data->image_len; + uint32_t length = unpadded_length + 1; // Add a byte for the checksum + length = (length + 15) & ~15; // Pad to next full 16 byte block + + // Verify checksum + uint8_t buf[16]; + esp_err_t err = bootloader_flash_read(data->start_addr + unpadded_length, buf, length - unpadded_length, true); + uint8_t calc = buf[length - unpadded_length - 1]; + uint8_t checksum = (checksum_word >> 24) + ^ (checksum_word >> 16) + ^ (checksum_word >> 8) + ^ (checksum_word >> 0); + if (err != ESP_OK || checksum != calc) { + ESP_LOGE(TAG, "Checksum failed. Calculated 0x%x read 0x%x", checksum, calc); + return ESP_ERR_IMAGE_INVALID; + } + if (sha_handle != NULL) { +// bootloader_sha256_data(sha_handle, buf, length - unpadded_length); + } + +// if (data->image.hash_appended) { +// // Account for the hash in the total image length +// length += HASH_LEN; +// } + data->image_len = length; + + return ESP_OK; +} + +static void debug_log_hash(const uint8_t *image_hash, const char *caption); + +static esp_err_t verify_secure_boot_signature(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 +} + +#endif diff --git a/components/bootloader_support/src/flash_partitions.c b/components/bootloader_support/src/flash_partitions.c index 69f7bafe..2e450ac0 100644 --- a/components/bootloader_support/src/flash_partitions.c +++ b/components/bootloader_support/src/flash_partitions.c @@ -91,3 +91,87 @@ esp_err_t esp_partition_table_basic_verify(const esp_partition_info_t *partition #endif +#ifdef CONFIG_TARGET_PLATFORM_ESP8266 + +#include + +#include "bootloader_config.h" + +#include "esp_err.h" +#include "esp_flash_partitions.h" +#include "esp_log.h" + +static const char *TAG = "flash_parts"; + +esp_err_t esp_partition_table_basic_verify(const esp_partition_info_t *partition_table, bool log_errors, int *num_partitions) +{ + int md5_found = 0; + int num_parts; + uint32_t chip_size = 2 * 1024 * 1024;// = g_rom_flashchip.chip_size; + *num_partitions = 0; + + for (num_parts = 0; num_parts < ESP_PARTITION_TABLE_MAX_ENTRIES; num_parts++) { +// const esp_partition_info_t *part = &partition_table[num_parts]; + esp_partition_info_t part_local; + esp_partition_info_t *part = &part_local;//partition_table[num_parts]; + + memcpy(&part_local, (intptr_t)partition_table + num_parts * sizeof(esp_partition_info_t), sizeof(esp_partition_info_t)); + + if (part->magic == ESP_PARTITION_MAGIC) { + const esp_partition_pos_t *pos = &part->pos; + if (pos->offset > chip_size || pos->offset + pos->size > chip_size) { + if (log_errors) { + ESP_LOGE(TAG, "partition %d invalid - offset 0x%x size 0x%x exceeds flash chip size 0x%x", + num_parts, pos->offset, pos->size, chip_size); + } + return ESP_ERR_INVALID_SIZE; + } + } else if (part->magic == ESP_PARTITION_MAGIC_MD5) { +#if 0 + if (md5_found) { + if (log_errors) { + ESP_LOGE(TAG, "Only one MD5 checksum is allowed"); + } + return ESP_ERR_INVALID_STATE; + } + + struct MD5Context context; + unsigned char digest[16]; + MD5Init(&context); + MD5Update(&context, (unsigned char *) partition_table, num_parts * sizeof(esp_partition_info_t)); + MD5Final(digest, &context); + + unsigned char *md5sum = ((unsigned char *) part) + 16; // skip the 2B magic number and the 14B fillup bytes + + if (memcmp(md5sum, digest, sizeof(digest)) != 0) { + if (log_errors) { + ESP_LOGE(TAG, "Incorrect MD5 checksum"); + } + return ESP_ERR_INVALID_STATE; + } + //MD5 checksum matches and we continue with the next interation in + //order to detect the end of the partition table + md5_found = 1; +#endif + } else if (part->magic == 0xFFFF + && part->type == PART_TYPE_END + && part->subtype == PART_SUBTYPE_END) { + ESP_LOGD(TAG, "partition table verified, %d entries", num_parts); + *num_partitions = num_parts - md5_found; //do not count the partition where the MD5 checksum is held + return ESP_OK; + } else { + if (log_errors) { + ESP_LOGE(TAG, "partition %d invalid magic number 0x%x", num_parts, part->magic); + } + return ESP_ERR_INVALID_STATE; + } + } + + if (log_errors) { + ESP_LOGE(TAG, "partition table has no terminating entry, not valid"); + } + return ESP_ERR_INVALID_STATE; +} + +#endif + diff --git a/components/spi_flash/include/esp_flash_data_types.h b/components/spi_flash/include/esp_flash_data_types.h index e6ccf228..136e0c1b 100644 --- a/components/spi_flash/include/esp_flash_data_types.h +++ b/components/spi_flash/include/esp_flash_data_types.h @@ -23,6 +23,7 @@ extern "C" #define ESP_PARTITION_TABLE_ADDR CONFIG_PARTITION_TABLE_OFFSET #define ESP_PARTITION_MAGIC 0x50AA +#define ESP_PARTITION_MAGIC_MD5 0xEBEB /* OTA selection structure (two copies in the OTA data partition.) Size of 32 bytes is friendly to flash encryption */