feat(bootloader): basic boot

This commit is contained in:
Wu Jian Gang
2018-06-19 16:53:58 +08:00
committed by Dong Heng
parent 1bc665a4ff
commit c7c96e0066
10 changed files with 1092 additions and 10 deletions

View File

@ -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 =

View File

@ -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;
}

View File

@ -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: */

View File

@ -15,7 +15,7 @@
#include <stdbool.h>
#include <esp_err.h>
#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.

View File

@ -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 <stdbool.h>
#include <stdint.h>
#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

View File

@ -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);

View File

@ -477,3 +477,345 @@ static void set_cache_and_start_app(
}
#endif
#ifdef CONFIG_TARGET_PLATFORM_ESP8266
#include <stdbool.h>
#include <sys/param.h>
#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

View File

@ -578,3 +578,546 @@ static void debug_log_hash(const uint8_t *image_hash, const char *label)
}
#endif
#ifdef CONFIG_TARGET_PLATFORM_ESP8266
#include <string.h>
#include <sys/param.h>
#include <esp_image_format.h>
#include <esp_secure_boot.h>
#include <esp_log.h>
#include <esp_spi_flash.h>
#include <bootloader_flash.h>
#include <bootloader_random.h>
#include <bootloader_sha.h>
static const char *TAG = "esp_image";
#define HASH_LEN 32 /* SHA-256 digest length */
#define SIXTEEN_MB 0x1000000
#define ESP_ROM_CHECKSUM_INITIAL 0xEF
/* 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

View File

@ -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 <stdbool.h>
#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

View File

@ -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 */