Merge branch 'feature/boot_time_optimisation' into 'master'
Optimise boot times, calculate SHA-256 hash of image during boot See merge request !939
This commit is contained in:
commit
e468cdee1d
20 changed files with 946 additions and 440 deletions
|
@ -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)
|
esp_err_t esp_ota_end(esp_ota_handle_t handle)
|
||||||
{
|
{
|
||||||
ota_ops_entry_t *it;
|
ota_ops_entry_t *it;
|
||||||
size_t image_size;
|
|
||||||
esp_err_t ret = ESP_OK;
|
esp_err_t ret = ESP_OK;
|
||||||
|
|
||||||
for (it = LIST_FIRST(&s_ota_ops_entries_head); it != NULL; it = LIST_NEXT(it, entries)) {
|
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;
|
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;
|
ret = ESP_ERR_OTA_VALIDATE_FAILED;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_SECURE_BOOT_ENABLED
|
#ifdef CONFIG_SECURE_BOOT_ENABLED
|
||||||
ret = esp_secure_boot_verify_signature(it->part->address, image_size);
|
ret = esp_secure_boot_verify_signature(it->part->address, data.image_len);
|
||||||
if (ret != ESP_OK) {
|
if (ret != ESP_OK) {
|
||||||
ret = ESP_ERR_OTA_VALIDATE_FAILED;
|
ret = ESP_ERR_OTA_VALIDATE_FAILED;
|
||||||
goto cleanup;
|
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)
|
esp_err_t esp_ota_set_boot_partition(const esp_partition_t *partition)
|
||||||
{
|
{
|
||||||
size_t image_size;
|
|
||||||
const esp_partition_t *find_partition = NULL;
|
const esp_partition_t *find_partition = NULL;
|
||||||
if (partition == NULL) {
|
if (partition == NULL) {
|
||||||
return ESP_ERR_INVALID_ARG;
|
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;
|
return ESP_ERR_OTA_VALIDATE_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_SECURE_BOOT_ENABLED
|
#ifdef CONFIG_SECURE_BOOT_ENABLED
|
||||||
esp_err_t ret = esp_secure_boot_verify_signature(partition->address, image_size);
|
esp_err_t ret = esp_secure_boot_verify_signature(partition->address, data.image_len);
|
||||||
if (ret != ESP_OK) {
|
if (ret != ESP_OK) {
|
||||||
return ESP_ERR_OTA_VALIDATE_FAILED;
|
return ESP_ERR_OTA_VALIDATE_FAILED;
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,16 @@ config BOOTLOADER_SPI_WP_PIN
|
||||||
|
|
||||||
The default value (GPIO 7) is correct for WP pin on ESP32-D2WD integrated flash.
|
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"
|
menu "Security features"
|
||||||
|
@ -217,7 +226,7 @@ config FLASH_ENCRYPTION_UART_BOOTLOADER_ALLOW_CACHE
|
||||||
config SECURE_BOOT_TEST_MODE
|
config SECURE_BOOT_TEST_MODE
|
||||||
bool "Secure boot test mode: don't permanently set any efuses"
|
bool "Secure boot test mode: don't permanently set any efuses"
|
||||||
depends on SECURE_BOOT_INSECURE
|
depends on SECURE_BOOT_INSECURE
|
||||||
default N
|
default n
|
||||||
help
|
help
|
||||||
If this option is set, all permanent secure boot changes (via Efuse) are disabled.
|
If this option is set, all permanent secure boot changes (via Efuse) are disabled.
|
||||||
|
|
||||||
|
|
|
@ -2,3 +2,8 @@
|
||||||
# paths can be added at this level (we need binary librtc to be
|
# paths can be added at this level (we need binary librtc to be
|
||||||
# available to link bootloader).
|
# available to link bootloader).
|
||||||
COMPONENT_SUBMODULES += $(IDF_PATH)/components/esp32/lib
|
COMPONENT_SUBMODULES += $(IDF_PATH)/components/esp32/lib
|
||||||
|
|
||||||
|
ifdef CONFIG_BOOTLOADER_LTO
|
||||||
|
CFLAGS += -flto
|
||||||
|
EXTRA_LDFLAGS += -Wl,-flto
|
||||||
|
endif
|
||||||
|
|
|
@ -53,17 +53,18 @@
|
||||||
|
|
||||||
extern int _bss_start;
|
extern int _bss_start;
|
||||||
extern int _bss_end;
|
extern int _bss_end;
|
||||||
|
extern int _data_start;
|
||||||
|
extern int _data_end;
|
||||||
|
|
||||||
static const char* TAG = "boot";
|
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();
|
void bootloader_main();
|
||||||
static void unpack_load_app(const esp_partition_pos_t *app_node);
|
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,
|
static void set_cache_and_start_app(uint32_t drom_addr,
|
||||||
uint32_t drom_load_addr,
|
uint32_t drom_load_addr,
|
||||||
uint32_t drom_size,
|
uint32_t drom_size,
|
||||||
|
@ -76,10 +77,26 @@ static void clock_configure(void);
|
||||||
static void uart_console_configure(void);
|
static void uart_console_configure(void);
|
||||||
static void wdt_reset_check(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();
|
cpu_configure_region_protection();
|
||||||
|
|
||||||
|
/* Sanity check that static RAM is after the stack */
|
||||||
|
#ifndef NDEBUG
|
||||||
|
{
|
||||||
|
int *sp = get_sp();
|
||||||
|
assert(&_bss_start <= &_bss_end);
|
||||||
|
assert(&_data_start <= &_data_end);
|
||||||
|
assert(sp < &_bss_start);
|
||||||
|
assert(sp < &_data_start);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
//Clear bss
|
//Clear bss
|
||||||
memset(&_bss_start, 0, (&_bss_end - &_bss_start) * sizeof(_bss_start));
|
memset(&_bss_start, 0, (&_bss_end - &_bss_start) * sizeof(_bss_start));
|
||||||
|
|
||||||
|
@ -253,7 +270,7 @@ void bootloader_main()
|
||||||
memset(&bs, 0, sizeof(bs));
|
memset(&bs, 0, sizeof(bs));
|
||||||
|
|
||||||
ESP_LOGI(TAG, "compile time " __TIME__ );
|
ESP_LOGI(TAG, "compile time " __TIME__ );
|
||||||
ets_set_appcpu_boot_addr(0);
|
ets_set_appcpu_boot_addr(0);
|
||||||
|
|
||||||
/* disable watch dog here */
|
/* disable watch dog here */
|
||||||
REG_CLR_BIT( RTC_CNTL_WDTCONFIG0_REG, RTC_CNTL_WDT_FLASHBOOT_MOD_EN );
|
REG_CLR_BIT( RTC_CNTL_WDTCONFIG0_REG, RTC_CNTL_WDT_FLASHBOOT_MOD_EN );
|
||||||
|
@ -276,7 +293,8 @@ void bootloader_main()
|
||||||
bootloader_enable_qio_mode();
|
bootloader_enable_qio_mode();
|
||||||
#endif
|
#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!");
|
ESP_LOGE(TAG, "failed to load bootloader header!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -408,33 +426,16 @@ void bootloader_main()
|
||||||
static void unpack_load_app(const esp_partition_pos_t* partition)
|
static void unpack_load_app(const esp_partition_pos_t* partition)
|
||||||
{
|
{
|
||||||
esp_err_t err;
|
esp_err_t err;
|
||||||
esp_image_header_t image_header;
|
esp_image_metadata_t data;
|
||||||
uint32_t image_length;
|
|
||||||
|
|
||||||
/* TODO: verify the app image as part of OTA boot decision, so can have fallbacks */
|
/* TODO: load the app image as part of OTA boot decision, so can fallback if loading fails */
|
||||||
err = esp_image_basic_verify(partition->offset, true, &image_length);
|
/* Loading the image here also includes secure boot verification */
|
||||||
|
err = esp_image_load(ESP_IMAGE_LOAD, partition, &data);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "Failed to verify app image @ 0x%x (%d)", partition->offset, err);
|
ESP_LOGE(TAG, "Failed to verify app image @ 0x%x (%d)", partition->offset, err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_SECURE_BOOT_ENABLED
|
|
||||||
if (esp_secure_boot_enabled()) {
|
|
||||||
ESP_LOGI(TAG, "Verifying app signature @ 0x%x (length 0x%x)", partition->offset, image_length);
|
|
||||||
err = esp_secure_boot_verify_signature(partition->offset, image_length);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
ESP_LOGE(TAG, "App image @ 0x%x failed signature verification (%d)", partition->offset, err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ESP_LOGD(TAG, "App signature is valid");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (esp_image_load_header(partition->offset, true, &image_header) != ESP_OK) {
|
|
||||||
ESP_LOGE(TAG, "Failed to load app image header @ 0x%x", partition->offset);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t drom_addr = 0;
|
uint32_t drom_addr = 0;
|
||||||
uint32_t drom_load_addr = 0;
|
uint32_t drom_load_addr = 0;
|
||||||
uint32_t drom_size = 0;
|
uint32_t drom_size = 0;
|
||||||
|
@ -442,124 +443,39 @@ static void unpack_load_app(const esp_partition_pos_t* partition)
|
||||||
uint32_t irom_load_addr = 0;
|
uint32_t irom_load_addr = 0;
|
||||||
uint32_t irom_size = 0;
|
uint32_t irom_size = 0;
|
||||||
|
|
||||||
/* Reload the RTC memory segments whenever a non-deepsleep reset
|
// Find DROM & IROM addresses, to configure cache mappings
|
||||||
is occurring */
|
for (int i = 0; i < data.image.segment_count; i++) {
|
||||||
bool load_rtc_memory = rtc_get_reset_reason(0) != DEEPSLEEP_RESET;
|
esp_image_segment_header_t *header = &data.segments[i];
|
||||||
|
if (header->load_addr >= SOC_IROM_LOW && header->load_addr < SOC_IROM_HIGH) {
|
||||||
ESP_LOGD(TAG, "bin_header: %u %u %u %u %08x", image_header.magic,
|
if (drom_addr != 0) {
|
||||||
image_header.segment_count,
|
ESP_LOGE(TAG, MAP_ERR_MSG, "DROM");
|
||||||
image_header.spi_mode,
|
} else {
|
||||||
image_header.spi_size,
|
ESP_LOGD(TAG, "Mapping segment %d as %s", i, "DROM");
|
||||||
(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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
drom_addr = data.segment_data[i];
|
||||||
const void *data = bootloader_mmap(data_offs, segment_header.data_len);
|
drom_load_addr = header->load_addr;
|
||||||
if(!data) {
|
drom_size = header->data_len;
|
||||||
ESP_LOGE(TAG, "bootloader_mmap(0x%xc, 0x%x) failed",
|
}
|
||||||
data_offs, segment_header.data_len);
|
if (header->load_addr >= SOC_DROM_LOW && header->load_addr < SOC_DROM_HIGH) {
|
||||||
return;
|
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);
|
irom_addr = data.segment_data[i];
|
||||||
bootloader_munmap(data);
|
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,
|
set_cache_and_start_app(drom_addr,
|
||||||
drom_load_addr,
|
drom_load_addr,
|
||||||
drom_size,
|
drom_size,
|
||||||
irom_addr,
|
irom_addr,
|
||||||
irom_load_addr,
|
irom_load_addr,
|
||||||
irom_size,
|
irom_size,
|
||||||
image_header.entry_addr);
|
data.image.entry_addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void set_cache_and_start_app(
|
static void set_cache_and_start_app(
|
||||||
|
@ -574,6 +490,14 @@ static void set_cache_and_start_app(
|
||||||
ESP_LOGD(TAG, "configure drom and irom and start");
|
ESP_LOGD(TAG, "configure drom and irom and start");
|
||||||
Cache_Read_Disable( 0 );
|
Cache_Read_Disable( 0 );
|
||||||
Cache_Flush( 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
|
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 );
|
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 );
|
int rc = cache_flash_mmu_set( 0, 0, drom_load_addr & 0xffff0000, drom_addr & 0xffff0000, 64, drom_page_count );
|
||||||
|
@ -632,7 +556,7 @@ static void update_flash_config(const esp_image_header_t* pfhdr)
|
||||||
Cache_Read_Enable( 0 );
|
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)
|
#if (BOOT_LOG_LEVEL >= BOOT_LOG_LEVEL_NOTICE)
|
||||||
|
|
||||||
|
@ -862,3 +786,9 @@ static void wdt_reset_check(void)
|
||||||
}
|
}
|
||||||
wdt_reset_cpu0_info_enable();
|
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) {}
|
||||||
|
}
|
||||||
|
|
|
@ -1,23 +1,21 @@
|
||||||
/*
|
/*
|
||||||
Linker file used to link the bootloader.
|
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 */
|
/* Simplified memory map for the bootloader
|
||||||
/* The load addresses are defined later using the AT statements. */
|
|
||||||
|
The main purpose is to make sure the bootloader can load into main memory
|
||||||
|
without overwriting itself.
|
||||||
|
*/
|
||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
/* All these values assume the flash cache is on, and have the blocks this uses subtracted from the length
|
/* I/O */
|
||||||
of the various regions. The 'data access port' dram/drom regions map to the same iram/irom regions but
|
dport0_seg (RW) : org = 0x3FF00000, len = 0x10
|
||||||
are connected to the data port of the CPU and eg allow bytewise access. */
|
/* 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 */
|
||||||
dport0_seg (RW) : org = 0x3FF00000, len = 0x10 /* IO */
|
iram_seg (RWX) : org = 0x40078000, len = 0x8000
|
||||||
iram_seg (RWX) : org = 0x40080000, len = 0x400 /* 1k of IRAM used by bootloader functions which need to flush/enable APP CPU cache */
|
/* 64k at the end of DRAM, after ROM bootloader stack */
|
||||||
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
|
||||||
dram_seg (RW) : org = 0x3FFF0000, len = 0x10000 /* 64k at the end of DRAM, after ROM bootloader stack */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Default entry point: */
|
/* Default entry point: */
|
||||||
|
@ -28,19 +26,10 @@ SECTIONS
|
||||||
{
|
{
|
||||||
.iram1.text :
|
.iram1.text :
|
||||||
{
|
{
|
||||||
_init_start = ABSOLUTE(.);
|
|
||||||
*(.UserEnter.literal);
|
|
||||||
*(.UserEnter.text);
|
|
||||||
. = ALIGN (16);
|
. = ALIGN (16);
|
||||||
*(.entry.text)
|
*(.entry.text)
|
||||||
*(.init.literal)
|
*(.init.literal)
|
||||||
*(.init)
|
*(.init)
|
||||||
_init_end = ABSOLUTE(.);
|
|
||||||
|
|
||||||
/* Code marked as runnning out of IRAM */
|
|
||||||
_iram_text_start = ABSOLUTE(.);
|
|
||||||
*(.iram1 .iram1.*)
|
|
||||||
_iram_text_end = ABSOLUTE(.);
|
|
||||||
} > iram_seg
|
} > iram_seg
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,7 +47,7 @@ SECTIONS
|
||||||
*(.sbss2.*)
|
*(.sbss2.*)
|
||||||
*(.gnu.linkonce.sb2.*)
|
*(.gnu.linkonce.sb2.*)
|
||||||
*(.dynbss)
|
*(.dynbss)
|
||||||
KEEP(*(.bss))
|
*(.bss)
|
||||||
*(.bss.*)
|
*(.bss.*)
|
||||||
*(.gnu.linkonce.b.*)
|
*(.gnu.linkonce.b.*)
|
||||||
*(COMMON)
|
*(COMMON)
|
||||||
|
@ -66,33 +55,28 @@ SECTIONS
|
||||||
_bss_end = ABSOLUTE(.);
|
_bss_end = ABSOLUTE(.);
|
||||||
} >dram_seg
|
} >dram_seg
|
||||||
|
|
||||||
|
|
||||||
.dram0.data :
|
.dram0.data :
|
||||||
{
|
{
|
||||||
_data_start = ABSOLUTE(.);
|
_data_start = ABSOLUTE(.);
|
||||||
KEEP(*(.data))
|
*(.data)
|
||||||
KEEP(*(.data.*))
|
*(.data.*)
|
||||||
KEEP(*(.gnu.linkonce.d.*))
|
*(.gnu.linkonce.d.*)
|
||||||
KEEP(*(.data1))
|
*(.data1)
|
||||||
KEEP(*(.sdata))
|
*(.sdata)
|
||||||
KEEP(*(.sdata.*))
|
*(.sdata.*)
|
||||||
KEEP(*(.gnu.linkonce.s.*))
|
*(.gnu.linkonce.s.*)
|
||||||
KEEP(*(.sdata2))
|
*(.sdata2)
|
||||||
KEEP(*(.sdata2.*))
|
*(.sdata2.*)
|
||||||
KEEP(*(.gnu.linkonce.s2.*))
|
*(.gnu.linkonce.s2.*)
|
||||||
KEEP(*(.jcr))
|
*(.jcr)
|
||||||
_data_end = ABSOLUTE(.);
|
_data_end = ABSOLUTE(.);
|
||||||
} >dram_seg
|
} >dram_seg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.dram0.rodata :
|
.dram0.rodata :
|
||||||
{
|
{
|
||||||
_rodata_start = ABSOLUTE(.);
|
_rodata_start = ABSOLUTE(.);
|
||||||
*(.rodata)
|
*(.rodata)
|
||||||
*(.rodata.*)
|
*(.rodata.*)
|
||||||
*(.irom1.text) /* catch stray ICACHE_RODATA_ATTR */
|
|
||||||
*(.gnu.linkonce.r.*)
|
*(.gnu.linkonce.r.*)
|
||||||
*(.rodata1)
|
*(.rodata1)
|
||||||
__XT_EXCEPTION_TABLE_ = ABSOLUTE(.);
|
__XT_EXCEPTION_TABLE_ = ABSOLUTE(.);
|
||||||
|
@ -132,17 +116,17 @@ SECTIONS
|
||||||
_heap_start = ABSOLUTE(.);
|
_heap_start = ABSOLUTE(.);
|
||||||
} >dram_seg
|
} >dram_seg
|
||||||
|
|
||||||
.iram_pool_1.text :
|
.iram.text :
|
||||||
{
|
{
|
||||||
_stext = .;
|
_stext = .;
|
||||||
_text_start = ABSOLUTE(.);
|
_text_start = ABSOLUTE(.);
|
||||||
*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)
|
*(.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.literal)
|
||||||
*(.fini)
|
*(.fini)
|
||||||
*(.gnu.version)
|
*(.gnu.version)
|
||||||
_text_end = ABSOLUTE(.);
|
_text_end = ABSOLUTE(.);
|
||||||
_etext = .;
|
_etext = .;
|
||||||
} >iram_pool_1_seg
|
} > iram_seg
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,11 @@
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
#ifndef __ESP32_IMAGE_FORMAT_H
|
#pragma once
|
||||||
#define __ESP32_IMAGE_FORMAT_H
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <esp_err.h>
|
#include <esp_err.h>
|
||||||
|
#include "esp_flash_partitions.h"
|
||||||
|
|
||||||
#define ESP_ERR_IMAGE_BASE 0x2000
|
#define ESP_ERR_IMAGE_BASE 0x2000
|
||||||
#define ESP_ERR_IMAGE_FLASH_FAIL (ESP_ERR_IMAGE_BASE + 1)
|
#define ESP_ERR_IMAGE_FLASH_FAIL (ESP_ERR_IMAGE_BASE + 1)
|
||||||
|
@ -59,13 +59,27 @@ typedef enum {
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t magic;
|
uint8_t magic;
|
||||||
uint8_t segment_count;
|
uint8_t segment_count;
|
||||||
uint8_t spi_mode; /* flash read mode (esp_image_spi_mode_t as uint8_t) */
|
/* 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_mode;
|
||||||
uint8_t spi_size: 4; /* flash chip size (esp_image_flash_size_t as uint8_t) */
|
/* 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;
|
uint32_t entry_addr;
|
||||||
uint8_t encrypt_flag; /* encrypt flag */
|
/* WP pin when SPI pins set via efuse (read by ROM bootloader, the IDF bootloader uses software to configure the WP
|
||||||
uint8_t extra_header[15]; /* ESP32 additional header, unused by second bootloader */
|
* pin and sets this field to 0xEE=disabled) */
|
||||||
} esp_image_header_t;
|
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 */
|
/* Header of binary image segment */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -73,62 +87,60 @@ typedef struct {
|
||||||
uint32_t data_len;
|
uint32_t data_len;
|
||||||
} esp_image_segment_header_t;
|
} esp_image_segment_header_t;
|
||||||
|
|
||||||
|
#define ESP_IMAGE_MAX_SEGMENTS 16
|
||||||
|
|
||||||
|
/* Structure to hold on-flash image metadata */
|
||||||
|
typedef struct {
|
||||||
|
uint32_t start_addr; /* Start address of image */
|
||||||
|
esp_image_header_t image; /* Header for entire image */
|
||||||
|
esp_image_segment_header_t segments[ESP_IMAGE_MAX_SEGMENTS]; /* Per-segment header data */
|
||||||
|
uint32_t segment_data[ESP_IMAGE_MAX_SEGMENTS]; /* Data offsets for each segment */
|
||||||
|
uint32_t image_len; /* Length of image on flash, in bytes */
|
||||||
|
} esp_image_metadata_t;
|
||||||
|
|
||||||
|
/* Mode selection for esp_image_load() */
|
||||||
|
typedef enum {
|
||||||
|
ESP_IMAGE_VERIFY, /* Verify image contents, load metadata. Print errorsors. */
|
||||||
|
ESP_IMAGE_VERIFY_SILENT, /* Verify image contents, load metadata. Don't print errors. */
|
||||||
|
#ifdef BOOTLOADER_BUILD
|
||||||
|
ESP_IMAGE_LOAD, /* Verify image contents, load to memory. Print errors. */
|
||||||
|
#endif
|
||||||
|
} esp_image_load_mode_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Read an ESP image header from flash.
|
* @brief Verify and (optionally, in bootloader mode) load an app image.
|
||||||
*
|
*
|
||||||
* If encryption is enabled, data will be transparently decrypted.
|
* 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 mode Mode of operation (verify, silent verify, or load).
|
||||||
* @param log_errors Log error output if image header appears invalid.
|
* @param part Partition to load the app from.
|
||||||
* @param[out] image_header Pointer to an esp_image_header_t struture to be filled with data. If the function fails, contents are undefined.
|
* @param[inout] data Pointer to the image metadata structure which is be filled in by this function. 'start_addr' member should be set (to the start address of the image.) Other fields will all be initialised by this function.
|
||||||
*
|
|
||||||
* @return ESP_OK if 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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Non-cryptographically validate app image integrity. On success, length of image is provided to caller.
|
|
||||||
*
|
|
||||||
* If the image has a secure boot signature appended, the signature is not checked and this length is not included in the
|
|
||||||
* output value.
|
|
||||||
*
|
*
|
||||||
* Image validation checks:
|
* Image validation checks:
|
||||||
* - Magic byte
|
* - Magic byte.
|
||||||
* - No single segment longer than 16MB
|
* - Partition smaller than 16MB.
|
||||||
* - Total image no longer than 16MB
|
* - All segments & image fit in partition.
|
||||||
* - 8 bit image checksum is valid
|
* - 8 bit image checksum is valid.
|
||||||
*
|
* - SHA-256 of image is valid (if image has this appended).
|
||||||
* If flash encryption is enabled, the image will be tranpsarently decrypted.
|
* - (Signature) if signature verification is enabled.
|
||||||
*
|
|
||||||
* @param src_addr Offset of the start of the image in flash. Must be 4 byte aligned.
|
|
||||||
* @param allow_decrypt If true and flash encryption is enabled, the image will be transparently decrypted.
|
|
||||||
* @param log_errors Log errors verifying the image.
|
|
||||||
* @param[out] length Length of the image, set to a value if the image is valid. Can be null.
|
|
||||||
*
|
|
||||||
* @return ESP_OK if image is valid, ESP_FAIL or ESP_ERR_IMAGE_INVALID on errors.
|
|
||||||
*
|
*
|
||||||
|
* @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_basic_verify(uint32_t src_addr, bool log_errors, uint32_t *length);
|
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.
|
||||||
|
*
|
||||||
|
* @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_verify_bootloader(uint32_t *length);
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -139,5 +151,3 @@ typedef struct {
|
||||||
uint32_t irom_load_addr;
|
uint32_t irom_load_addr;
|
||||||
uint32_t irom_size;
|
uint32_t irom_size;
|
||||||
} esp_image_flash_mapping_t;
|
} esp_image_flash_mapping_t;
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -11,13 +11,16 @@
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
#ifndef __ESP32_SECUREBOOT_H
|
#pragma once
|
||||||
#define __ESP32_SECUREBOOT_H
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <esp_err.h>
|
#include <esp_err.h>
|
||||||
#include "soc/efuse_reg.h"
|
#include "soc/efuse_reg.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Support functions for secure boot features.
|
/* Support functions for secure boot features.
|
||||||
|
|
||||||
Can be compiled as part of app or bootloader code.
|
Can be compiled as part of app or bootloader code.
|
||||||
|
@ -74,12 +77,22 @@ esp_err_t esp_secure_boot_permanently_enable(void);
|
||||||
*/
|
*/
|
||||||
esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length);
|
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. */
|
/** @brief Secure boot verification block, on-flash data format. */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t version;
|
uint32_t version;
|
||||||
uint8_t signature[64];
|
uint8_t signature[64];
|
||||||
} esp_secure_boot_sig_block_t;
|
} 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
|
#define FLASH_OFFS_SECURE_BOOT_IV_DIGEST 0
|
||||||
|
|
||||||
/** @brief Secure boot IV+digest header */
|
/** @brief Secure boot IV+digest header */
|
||||||
|
@ -88,4 +101,7 @@ typedef struct {
|
||||||
uint8_t digest[64];
|
uint8_t digest[64];
|
||||||
} esp_secure_boot_iv_digest_t;
|
} esp_secure_boot_iv_digest_t;
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
32
components/bootloader_support/include_priv/bootloader_sha.h
Normal file
32
components/bootloader_support/include_priv/bootloader_sha.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/* Provide a SHA256 API for bootloader_support code,
|
||||||
|
that can be used from bootloader or app code.
|
||||||
|
|
||||||
|
This header is available to source code in the bootloader & bootloader_support components only.
|
||||||
|
Use mbedTLS APIs or include hwcrypto/sha.h to calculate SHA256 in IDF apps.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
typedef void *bootloader_sha256_handle_t;
|
||||||
|
|
||||||
|
bootloader_sha256_handle_t bootloader_sha256_start();
|
||||||
|
|
||||||
|
void bootloader_sha256_data(bootloader_sha256_handle_t handle, const void *data, size_t data_len);
|
||||||
|
|
||||||
|
void bootloader_sha256_finish(bootloader_sha256_handle_t handle, uint8_t *digest);
|
|
@ -32,11 +32,13 @@ const void *bootloader_mmap(uint32_t src_addr, uint32_t size)
|
||||||
return NULL; /* existing mapping in use... */
|
return NULL; /* existing mapping in use... */
|
||||||
}
|
}
|
||||||
const void *result = NULL;
|
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) {
|
if (err != ESP_OK) {
|
||||||
result = NULL;
|
result = NULL;
|
||||||
}
|
}
|
||||||
return result;
|
return (void *)((intptr_t)result + (src_addr - src_page));
|
||||||
}
|
}
|
||||||
|
|
||||||
void bootloader_munmap(const void *mapping)
|
void bootloader_munmap(const void *mapping)
|
||||||
|
@ -88,6 +90,7 @@ static const char *TAG = "bootloader_flash";
|
||||||
|
|
||||||
static bool mapped;
|
static bool mapped;
|
||||||
|
|
||||||
|
// Current bootloader mapping (ab)used for bootloader_read()
|
||||||
static uint32_t current_read_mapping = UINT32_MAX;
|
static uint32_t current_read_mapping = UINT32_MAX;
|
||||||
|
|
||||||
const void *bootloader_mmap(uint32_t src_addr, uint32_t size)
|
const void *bootloader_mmap(uint32_t src_addr, uint32_t size)
|
||||||
|
|
166
components/bootloader_support/src/bootloader_sha.c
Normal file
166
components/bootloader_support/src/bootloader_sha.c
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#include "bootloader_sha.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <sys/param.h>
|
||||||
|
|
||||||
|
#ifndef BOOTLOADER_BUILD
|
||||||
|
// App version is a wrapper around mbedTLS SHA API
|
||||||
|
#include <mbedtls/sha256.h>
|
||||||
|
|
||||||
|
bootloader_sha256_handle_t bootloader_sha256_start()
|
||||||
|
{
|
||||||
|
mbedtls_sha256_context *ctx = (mbedtls_sha256_context *)malloc(sizeof(mbedtls_sha256_context));
|
||||||
|
if (!ctx) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
mbedtls_sha256_init(ctx);
|
||||||
|
mbedtls_sha256_starts(ctx, false);
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bootloader_sha256_data(bootloader_sha256_handle_t handle, const void *data, size_t data_len)
|
||||||
|
{
|
||||||
|
assert(handle != NULL);
|
||||||
|
mbedtls_sha256_context *ctx = (mbedtls_sha256_context *)handle;
|
||||||
|
mbedtls_sha256_update(ctx, data, data_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bootloader_sha256_finish(bootloader_sha256_handle_t handle, uint8_t *digest)
|
||||||
|
{
|
||||||
|
assert(handle != NULL);
|
||||||
|
mbedtls_sha256_context *ctx = (mbedtls_sha256_context *)handle;
|
||||||
|
if (digest != NULL) {
|
||||||
|
mbedtls_sha256_finish(ctx, digest);
|
||||||
|
}
|
||||||
|
mbedtls_sha256_free(ctx);
|
||||||
|
free(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // Bootloader version
|
||||||
|
|
||||||
|
#include "rom/sha.h"
|
||||||
|
#include "soc/dport_reg.h"
|
||||||
|
#include "soc/hwcrypto_reg.h"
|
||||||
|
|
||||||
|
#include "rom/ets_sys.h" // TO REMOVE
|
||||||
|
|
||||||
|
static uint32_t words_hashed;
|
||||||
|
|
||||||
|
// Words per SHA256 block
|
||||||
|
static const size_t BLOCK_WORDS = (64/sizeof(uint32_t));
|
||||||
|
// Words in final SHA256 digest
|
||||||
|
static const size_t DIGEST_WORDS = (32/sizeof(uint32_t));
|
||||||
|
|
||||||
|
bootloader_sha256_handle_t bootloader_sha256_start()
|
||||||
|
{
|
||||||
|
// Enable SHA hardware
|
||||||
|
ets_sha_enable();
|
||||||
|
words_hashed = 0;
|
||||||
|
return (bootloader_sha256_handle_t)&words_hashed; // Meaningless non-NULL value
|
||||||
|
}
|
||||||
|
|
||||||
|
void bootloader_sha256_data(bootloader_sha256_handle_t handle, const void *data, size_t data_len)
|
||||||
|
{
|
||||||
|
assert(handle != NULL);
|
||||||
|
assert(data_len % 4 == 0);
|
||||||
|
|
||||||
|
const uint32_t *w = (const uint32_t *)data;
|
||||||
|
size_t word_len = data_len / 4;
|
||||||
|
uint32_t *sha_text_reg = (uint32_t *)(SHA_TEXT_BASE);
|
||||||
|
|
||||||
|
//ets_printf("word_len %d so far %d\n", word_len, words_hashed);
|
||||||
|
while (word_len > 0) {
|
||||||
|
size_t block_count = words_hashed % BLOCK_WORDS;
|
||||||
|
size_t copy_words = (BLOCK_WORDS - block_count);
|
||||||
|
|
||||||
|
copy_words = MIN(word_len, copy_words);
|
||||||
|
|
||||||
|
// Wait for SHA engine idle
|
||||||
|
while(REG_READ(SHA_256_BUSY_REG) != 0) { }
|
||||||
|
|
||||||
|
// Copy to memory block
|
||||||
|
//ets_printf("block_count %d copy_words %d\n", block_count, copy_words);
|
||||||
|
for (int i = 0; i < copy_words; i++) {
|
||||||
|
sha_text_reg[block_count + i] = __builtin_bswap32(w[i]);
|
||||||
|
}
|
||||||
|
asm volatile ("memw");
|
||||||
|
|
||||||
|
// Update counters
|
||||||
|
words_hashed += copy_words;
|
||||||
|
block_count += copy_words;
|
||||||
|
word_len -= copy_words;
|
||||||
|
w += copy_words;
|
||||||
|
|
||||||
|
// If we loaded a full block, run the SHA engine
|
||||||
|
if (block_count == BLOCK_WORDS) {
|
||||||
|
//ets_printf("running engine @ count %d\n", words_hashed);
|
||||||
|
if (words_hashed == BLOCK_WORDS) {
|
||||||
|
REG_WRITE(SHA_256_START_REG, 1);
|
||||||
|
} else {
|
||||||
|
REG_WRITE(SHA_256_CONTINUE_REG, 1);
|
||||||
|
}
|
||||||
|
block_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void bootloader_sha256_finish(bootloader_sha256_handle_t handle, uint8_t *digest)
|
||||||
|
{
|
||||||
|
assert(handle != NULL);
|
||||||
|
|
||||||
|
if (digest == NULL) {
|
||||||
|
return; // We'd free resources here, but there are none to free
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t data_words = words_hashed;
|
||||||
|
|
||||||
|
// Pad to a 55 byte long block loaded in the engine
|
||||||
|
// (leaving 1 byte 0x80 plus variable padding plus 8 bytes of length,
|
||||||
|
// to fill a 64 byte block.)
|
||||||
|
int block_bytes = (words_hashed % BLOCK_WORDS) * 4;
|
||||||
|
int pad_bytes = 55 - block_bytes;
|
||||||
|
if (pad_bytes < 0) {
|
||||||
|
pad_bytes += 64;
|
||||||
|
}
|
||||||
|
static const uint8_t padding[64] = { 0x80, 0, };
|
||||||
|
|
||||||
|
pad_bytes += 5; // 1 byte for 0x80 plus first 4 bytes of the 64-bit length
|
||||||
|
assert(pad_bytes % 4 == 0); // should be, as (block_bytes % 4 == 0)
|
||||||
|
|
||||||
|
bootloader_sha256_data(handle, padding, pad_bytes);
|
||||||
|
|
||||||
|
assert(words_hashed % BLOCK_WORDS == 60/4); // 32-bits left in block
|
||||||
|
|
||||||
|
// Calculate 32-bit length for final 32 bits of data
|
||||||
|
uint32_t bit_count = __builtin_bswap32( data_words * 32 );
|
||||||
|
bootloader_sha256_data(handle, &bit_count, sizeof(bit_count));
|
||||||
|
|
||||||
|
assert(words_hashed % BLOCK_WORDS == 0);
|
||||||
|
|
||||||
|
while(REG_READ(SHA_256_BUSY_REG) == 1) { }
|
||||||
|
REG_WRITE(SHA_256_LOAD_REG, 1);
|
||||||
|
while(REG_READ(SHA_256_BUSY_REG) == 1) { }
|
||||||
|
|
||||||
|
uint32_t *digest_words = (uint32_t *)digest;
|
||||||
|
uint32_t *sha_text_reg = (uint32_t *)(SHA_TEXT_BASE);
|
||||||
|
for (int i = 0; i < DIGEST_WORDS; i++) {
|
||||||
|
digest_words[i] = __builtin_bswap32(sha_text_reg[i]);
|
||||||
|
}
|
||||||
|
asm volatile ("memw");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -12,178 +12,520 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/param.h>
|
||||||
|
|
||||||
|
#include <rom/rtc.h>
|
||||||
|
#include <soc/cpu.h>
|
||||||
#include <esp_image_format.h>
|
#include <esp_image_format.h>
|
||||||
|
#include <esp_secure_boot.h>
|
||||||
#include <esp_log.h>
|
#include <esp_log.h>
|
||||||
#include <bootloader_flash.h>
|
#include <bootloader_flash.h>
|
||||||
|
#include <bootloader_random.h>
|
||||||
|
#include <bootloader_sha.h>
|
||||||
|
|
||||||
static const char *TAG = "esp_image";
|
static const char *TAG = "esp_image";
|
||||||
|
|
||||||
|
#define HASH_LEN 32 /* SHA-256 digest length */
|
||||||
|
|
||||||
#define SIXTEEN_MB 0x1000000
|
#define SIXTEEN_MB 0x1000000
|
||||||
#define ESP_ROM_CHECKSUM_INITIAL 0xEF
|
#define ESP_ROM_CHECKSUM_INITIAL 0xEF
|
||||||
|
|
||||||
esp_err_t esp_image_load_header(uint32_t src_addr, bool log_errors, esp_image_header_t *image_header)
|
/* Headroom to ensure between stack SP (at time of checking) and data loaded from flash */
|
||||||
{
|
#define STACK_LOAD_HEADROOM 32768
|
||||||
esp_err_t err;
|
|
||||||
ESP_LOGD(TAG, "reading image header @ 0x%x", src_addr);
|
#ifdef BOOTLOADER_BUILD
|
||||||
|
/* 64 bits of random data to obfuscate loaded RAM with, until verification is complete
|
||||||
err = bootloader_flash_read(src_addr, image_header, sizeof(esp_image_header_t), true);
|
(Means loaded code isn't executable until after the secure boot check.)
|
||||||
|
*/
|
||||||
if (err == ESP_OK) {
|
static uint32_t ram_obfs_value[2];
|
||||||
if (image_header->magic != ESP_IMAGE_HEADER_MAGIC) {
|
#endif
|
||||||
if (log_errors) {
|
|
||||||
ESP_LOGE(TAG, "image at 0x%x has invalid magic byte", src_addr);
|
/* Return true if load_addr is an address the bootloader should load into */
|
||||||
}
|
static bool should_load(uint32_t load_addr);
|
||||||
err = ESP_ERR_IMAGE_INVALID;
|
/* Return true if load_addr is an address the bootloader should map via flash cache */
|
||||||
}
|
static bool should_map(uint32_t load_addr);
|
||||||
if (log_errors) {
|
|
||||||
if (image_header->spi_mode > ESP_IMAGE_SPI_MODE_SLOW_READ) {
|
/* Load or verify a segment */
|
||||||
ESP_LOGW(TAG, "image at 0x%x has invalid SPI mode %d", src_addr, image_header->spi_mode);
|
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);
|
||||||
}
|
|
||||||
if (image_header->spi_speed > ESP_IMAGE_SPI_SPEED_80M) {
|
/* Verify the main image header */
|
||||||
ESP_LOGW(TAG, "image at 0x%x has invalid SPI speed %d", src_addr, image_header->spi_speed);
|
static esp_err_t verify_image_header(uint32_t src_addr, const esp_image_header_t *image, bool silent);
|
||||||
}
|
|
||||||
if (image_header->spi_size > ESP_IMAGE_FLASH_SIZE_MAX) {
|
/* Verify a segment header */
|
||||||
ESP_LOGW(TAG, "image at 0x%x has invalid SPI size %d", src_addr, image_header->spi_size);
|
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) { \
|
||||||
if (err != ESP_OK) {
|
ESP_LOGE(TAG, __VA_ARGS__); \
|
||||||
bzero(image_header, sizeof(esp_image_header_t));
|
} \
|
||||||
}
|
goto err; \
|
||||||
return err;
|
} \
|
||||||
}
|
while(0)
|
||||||
|
|
||||||
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)
|
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
|
||||||
|
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;
|
esp_err_t err = ESP_OK;
|
||||||
uint32_t next_addr = src_addr + sizeof(esp_image_header_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;
|
||||||
|
|
||||||
if(index >= image_header->segment_count) {
|
if (data == NULL || part == NULL) {
|
||||||
if (log_errors) {
|
|
||||||
ESP_LOGE(TAG, "index %d higher than segment count %d", index, image_header->segment_count);
|
|
||||||
}
|
|
||||||
return ESP_ERR_INVALID_ARG;
|
return ESP_ERR_INVALID_ARG;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(int i = 0; i <= index && err == ESP_OK; i++) {
|
if (part->size > SIXTEEN_MB) {
|
||||||
|
err = ESP_ERR_INVALID_ARG;
|
||||||
|
FAIL_LOAD("partition size %d invalid, larger than 16MB", part->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
bzero(data, sizeof(esp_image_metadata_t));
|
||||||
|
data->start_addr = part->offset;
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "reading image header @ 0x%x", data->start_addr);
|
||||||
|
err = bootloader_flash_read(data->start_addr, &data->image, sizeof(esp_image_header_t), true);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate SHA-256 of image if secure boot is on, or if image has a hash appended
|
||||||
|
#ifdef CONFIG_SECURE_BOOT_ENABLED
|
||||||
|
if (1) {
|
||||||
|
#else
|
||||||
|
if (data->image.hash_appended) {
|
||||||
|
#endif
|
||||||
|
sha_handle = bootloader_sha256_start();
|
||||||
|
if (sha_handle == NULL) {
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
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);
|
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);
|
err = process_segment(i, next_addr, header, silent, do_load, sha_handle, &checksum_word);
|
||||||
if (err == ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
if ((segment_header->data_len & 3) != 0
|
goto err;
|
||||||
|| segment_header->data_len >= SIXTEEN_MB) {
|
}
|
||||||
if (log_errors) {
|
next_addr += sizeof(esp_image_segment_header_t);
|
||||||
ESP_LOGE(TAG, "invalid segment length 0x%x", segment_header->data_len);
|
data->segment_data[i] = next_addr;
|
||||||
}
|
next_addr += header->data_len;
|
||||||
err = ESP_ERR_IMAGE_INVALID;
|
}
|
||||||
}
|
|
||||||
next_addr += sizeof(esp_image_segment_header_t);
|
// Segments all loaded, verify length
|
||||||
ESP_LOGV(TAG, "segment data length 0x%x data starts 0x%x", segment_header->data_len, next_addr);
|
uint32_t end_addr = next_addr;
|
||||||
*segment_data_offset = next_addr;
|
if (end_addr < data->start_addr) {
|
||||||
next_addr += segment_header->data_len;
|
FAIL_LOAD("image offset has wrapped");
|
||||||
|
}
|
||||||
|
|
||||||
|
data->image_len = end_addr - data->start_addr;
|
||||||
|
ESP_LOGV(TAG, "image start 0x%08x end of last section 0x%08x", data->start_addr, end_addr);
|
||||||
|
err = verify_checksum(sha_handle, checksum_word, data);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (err != ESP_OK) {
|
#ifdef BOOTLOADER_BUILD
|
||||||
*segment_data_offset = 0;
|
if (do_load) { // Need to deobfuscate RAM
|
||||||
bzero(segment_header, sizeof(esp_image_segment_header_t));
|
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;
|
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 = 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;
|
esp_err_t err;
|
||||||
uint8_t buf[128];
|
|
||||||
uint8_t checksum = ESP_ROM_CHECKSUM_INITIAL;
|
|
||||||
esp_image_header_t image_header;
|
|
||||||
esp_image_segment_header_t segment_header = { 0 };
|
|
||||||
uint32_t segment_data_offs = 0;
|
|
||||||
uint32_t end_addr;
|
|
||||||
uint32_t length;
|
|
||||||
|
|
||||||
if (p_length != NULL) {
|
/* read segment header */
|
||||||
*p_length = 0;
|
err = bootloader_flash_read(flash_addr, header, sizeof(esp_image_segment_header_t), true);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "bootloader_flash_read failed at 0x%08x", flash_addr);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
if (sha_handle != NULL) {
|
||||||
|
bootloader_sha256_data(sha_handle, header, sizeof(esp_image_segment_header_t));
|
||||||
}
|
}
|
||||||
|
|
||||||
err = esp_image_load_header(src_addr, log_errors, &image_header);
|
intptr_t load_addr = header->load_addr;
|
||||||
|
uint32_t data_len = header->data_len;
|
||||||
|
uint32_t data_addr = flash_addr + sizeof(esp_image_segment_header_t);
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "segment data length 0x%x data starts 0x%x", data_len, data_addr);
|
||||||
|
|
||||||
|
err = verify_segment_header(index, header, data_addr, silent);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGD(TAG, "reading %d image segments", image_header.segment_count);
|
if (data_len % 4 != 0) {
|
||||||
|
FAIL_LOAD("unaligned segment length 0x%x", data_len);
|
||||||
|
}
|
||||||
|
|
||||||
/* Checksum each segment's data */
|
bool is_mapping = should_map(load_addr);
|
||||||
for (int i = 0; i < image_header.segment_count; i++) {
|
do_load = do_load && should_load(load_addr);
|
||||||
err = esp_image_load_segment_header(i, src_addr, &image_header, log_errors,
|
|
||||||
&segment_header, &segment_data_offs);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t load_addr = segment_header.load_addr;
|
if (!silent) {
|
||||||
bool map_segment = (load_addr >= SOC_DROM_LOW && load_addr < SOC_DROM_HIGH)
|
ESP_LOGI(TAG, "segment %d: paddr=0x%08x vaddr=0x%08x size=0x%05x (%6d) %s",
|
||||||
|| (load_addr >= SOC_IROM_LOW && load_addr < SOC_IROM_HIGH);
|
index, data_addr, load_addr,
|
||||||
|
data_len, data_len,
|
||||||
|
(do_load)?"load":(is_mapping)?"map":"");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (do_load) {
|
||||||
/* Check that flash cache mapped segment aligns correctly from flash it's mapped address,
|
/* Before loading segment, check it doesn't clobber bootloader RAM... */
|
||||||
relative to the 64KB page mapping size.
|
uint32_t end_addr = load_addr + data_len;
|
||||||
*/
|
if (end_addr < 0x40000000) {
|
||||||
ESP_LOGV(TAG, "segment %d map_segment %d segment_data_offs 0x%x load_addr 0x%x",
|
intptr_t sp = (intptr_t)get_sp();
|
||||||
i, map_segment, segment_data_offs, load_addr);
|
if (end_addr > sp - STACK_LOAD_HEADROOM) {
|
||||||
if (map_segment && ((segment_data_offs % SPI_FLASH_MMU_PAGE_SIZE) != (load_addr % SPI_FLASH_MMU_PAGE_SIZE))) {
|
ESP_LOGE(TAG, "Segment %d end address 0x%08x too high (bootloader stack 0x%08x liimit 0x%08x)",
|
||||||
ESP_LOGE(TAG, "Segment %d has load address 0x%08x, conflict with segment data at 0x%08x",
|
index, end_addr, sp, sp - STACK_LOAD_HEADROOM);
|
||||||
i, load_addr, segment_data_offs);
|
return ESP_ERR_IMAGE_INVALID;
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* End of image, verify checksum */
|
const uint32_t *data = (const uint32_t *)bootloader_mmap(data_addr, data_len);
|
||||||
end_addr = segment_data_offs + segment_header.data_len;
|
if(!data) {
|
||||||
|
ESP_LOGE(TAG, "bootloader_mmap(0x%x, 0x%x) failed",
|
||||||
|
data_addr, data_len);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
if (end_addr < src_addr) {
|
#ifdef BOOTLOADER_BUILD
|
||||||
if (log_errors) {
|
// Set up the obfuscation value to use for loading
|
||||||
ESP_LOGE(TAG, "image offset has wrapped");
|
while (ram_obfs_value[0] == 0 || ram_obfs_value[1] == 0) {
|
||||||
|
bootloader_fill_random(ram_obfs_value, sizeof(ram_obfs_value));
|
||||||
|
}
|
||||||
|
uint32_t *dest = (uint32_t *)load_addr;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const uint32_t *src = data;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
err:
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
err = ESP_ERR_IMAGE_INVALID;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t verify_segment_header(int index, const esp_image_segment_header_t *segment, uint32_t segment_data_offs, bool silent)
|
||||||
|
{
|
||||||
|
if ((segment->data_len & 3) != 0
|
||||||
|
|| segment->data_len >= SIXTEEN_MB) {
|
||||||
|
if (!silent) {
|
||||||
|
ESP_LOGE(TAG, "invalid segment length 0x%x", segment->data_len);
|
||||||
}
|
}
|
||||||
return ESP_ERR_IMAGE_INVALID;
|
return ESP_ERR_IMAGE_INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
length = end_addr - src_addr;
|
uint32_t load_addr = segment->load_addr;
|
||||||
if (length >= SIXTEEN_MB) {
|
bool map_segment = should_map(load_addr);
|
||||||
if (log_errors) {
|
|
||||||
ESP_LOGE(TAG, "invalid total length 0x%x", length);
|
/* 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_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_ERR_IMAGE_INVALID;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p_length != NULL) {
|
|
||||||
*p_length = length;
|
|
||||||
}
|
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool should_map(uint32_t load_addr)
|
||||||
|
{
|
||||||
|
return (load_addr >= SOC_IROM_LOW && load_addr < SOC_IROM_HIGH)
|
||||||
|
|| (load_addr >= SOC_DROM_LOW && load_addr < SOC_DROM_HIGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool should_load(uint32_t load_addr)
|
||||||
|
{
|
||||||
|
/* Reload the RTC memory segments whenever a non-deepsleep reset
|
||||||
|
is occurring */
|
||||||
|
bool load_rtc_memory = rtc_get_reset_reason(0) != DEEPSLEEP_RESET;
|
||||||
|
|
||||||
|
if (should_map(load_addr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (load_addr < 0x10000000) {
|
||||||
|
// Reserved for non-loaded addresses.
|
||||||
|
// Current reserved values are
|
||||||
|
// 0x0 (padding block)
|
||||||
|
// 0x4 (unused, but reserved for an MD5 block)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!load_rtc_memory) {
|
||||||
|
if (load_addr >= SOC_RTC_IRAM_LOW && load_addr < SOC_RTC_IRAM_HIGH) {
|
||||||
|
ESP_LOGD(TAG, "Skipping RTC code segment at 0x%08x\n", load_addr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (load_addr >= SOC_RTC_DATA_LOW && load_addr < SOC_RTC_DATA_HIGH) {
|
||||||
|
ESP_LOGD(TAG, "Skipping RTC data segment at 0x%08x\n", load_addr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t esp_image_verify_bootloader(uint32_t *length)
|
||||||
|
{
|
||||||
|
esp_image_metadata_t data;
|
||||||
|
const esp_partition_pos_t bootloader_part = {
|
||||||
|
.offset = ESP_BOOTLOADER_OFFSET,
|
||||||
|
.size = ESP_PARTITION_TABLE_OFFSET - ESP_BOOTLOADER_OFFSET,
|
||||||
|
};
|
||||||
|
esp_err_t err = esp_image_load(ESP_IMAGE_VERIFY,
|
||||||
|
&bootloader_part,
|
||||||
|
&data);
|
||||||
|
if (length != NULL) {
|
||||||
|
*length = (err == ESP_OK) ? data.image_len : 0;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t verify_checksum(bootloader_sha256_handle_t sha_handle, uint32_t checksum_word, esp_image_metadata_t *data)
|
||||||
|
{
|
||||||
|
uint32_t unpadded_length = data->image_len;
|
||||||
|
uint32_t length = unpadded_length + 1; // Add a byte for the checksum
|
||||||
|
length = (length + 15) & ~15; // Pad to next full 16 byte block
|
||||||
|
|
||||||
|
// Verify checksum
|
||||||
|
uint8_t buf[16];
|
||||||
|
esp_err_t err = bootloader_flash_read(data->start_addr + unpadded_length, buf, length - unpadded_length, true);
|
||||||
|
uint8_t calc = buf[length - unpadded_length - 1];
|
||||||
|
uint8_t checksum = (checksum_word >> 24)
|
||||||
|
^ (checksum_word >> 16)
|
||||||
|
^ (checksum_word >> 8)
|
||||||
|
^ (checksum_word >> 0);
|
||||||
|
if (err != ESP_OK || checksum != calc) {
|
||||||
|
ESP_LOGE(TAG, "Checksum failed. Calculated 0x%x read 0x%x", checksum, calc);
|
||||||
|
return ESP_ERR_IMAGE_INVALID;
|
||||||
|
}
|
||||||
|
if (sha_handle != NULL) {
|
||||||
|
bootloader_sha256_data(sha_handle, buf, length - unpadded_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data->image.hash_appended) {
|
||||||
|
// Account for the hash in the total image length
|
||||||
|
length += HASH_LEN;
|
||||||
|
}
|
||||||
|
data->image_len = length;
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void debug_log_hash(const uint8_t *image_hash, const char *caption);
|
||||||
|
|
||||||
|
static esp_err_t verify_secure_boot(bootloader_sha256_handle_t sha_handle, esp_image_metadata_t *data)
|
||||||
|
{
|
||||||
|
uint8_t image_hash[HASH_LEN] = { 0 };
|
||||||
|
|
||||||
|
// For secure boot, we calculate the signature hash over the whole file, which includes any "simple" hash
|
||||||
|
// appended to the image for corruption detection
|
||||||
|
if (data->image.hash_appended) {
|
||||||
|
const void *simple_hash = bootloader_mmap(data->start_addr + data->image_len - HASH_LEN, HASH_LEN);
|
||||||
|
bootloader_sha256_data(sha_handle, simple_hash, HASH_LEN);
|
||||||
|
bootloader_munmap(simple_hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
bootloader_sha256_finish(sha_handle, image_hash);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
|
@ -210,8 +210,8 @@ static esp_err_t encrypt_bootloader()
|
||||||
{
|
{
|
||||||
esp_err_t err;
|
esp_err_t err;
|
||||||
uint32_t image_length;
|
uint32_t image_length;
|
||||||
/* Check for plaintext bootloader */
|
/* Check for plaintext bootloader (verification will fail if it's already encrypted) */
|
||||||
if (esp_image_basic_verify(ESP_BOOTLOADER_OFFSET, false, &image_length) == ESP_OK) {
|
if (esp_image_verify_bootloader(&image_length) == ESP_OK) {
|
||||||
ESP_LOGD(TAG, "bootloader is plaintext. Encrypting...");
|
ESP_LOGD(TAG, "bootloader is plaintext. Encrypting...");
|
||||||
err = esp_flash_encrypt_region(ESP_BOOTLOADER_OFFSET, image_length);
|
err = esp_flash_encrypt_region(ESP_BOOTLOADER_OFFSET, image_length);
|
||||||
if (err != ESP_OK) {
|
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)
|
static esp_err_t encrypt_partition(int index, const esp_partition_info_t *partition)
|
||||||
{
|
{
|
||||||
esp_err_t err;
|
esp_err_t err;
|
||||||
uint32_t image_len = partition->pos.size;
|
|
||||||
bool should_encrypt = (partition->flags & PART_FLAG_ENCRYPTED);
|
bool should_encrypt = (partition->flags & PART_FLAG_ENCRYPTED);
|
||||||
|
|
||||||
if (partition->type == PART_TYPE_APP) {
|
if (partition->type == PART_TYPE_APP) {
|
||||||
/* check if the partition holds an unencrypted app */
|
/* check if the partition holds a valid unencrypted app */
|
||||||
if (esp_image_basic_verify(partition->pos.offset, false, &image_len) == ESP_OK) {
|
esp_image_metadata_t data_ignored;
|
||||||
if(image_len > partition->pos.size) {
|
err = esp_image_load(ESP_IMAGE_VERIFY,
|
||||||
ESP_LOGE(TAG, "partition entry %d has image longer than partition (%d vs %d)", index, image_len, partition->pos.size);
|
&partition->pos,
|
||||||
should_encrypt = false;
|
&data_ignored);
|
||||||
} else {
|
should_encrypt = (err == ESP_OK);
|
||||||
should_encrypt = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
should_encrypt = false;
|
|
||||||
}
|
|
||||||
} else if (partition->type == PART_TYPE_DATA && partition->subtype == PART_SUBTYPE_DATA_OTA) {
|
} 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 */
|
/* check if we have ota data partition and the partition should be encrypted unconditionally */
|
||||||
should_encrypt = true;
|
should_encrypt = true;
|
||||||
|
|
|
@ -67,7 +67,7 @@ static bool secure_boot_generate(uint32_t image_len){
|
||||||
}
|
}
|
||||||
|
|
||||||
/* generate digest from image contents */
|
/* generate digest from image contents */
|
||||||
image = bootloader_mmap(0x1000, image_len);
|
image = bootloader_mmap(ESP_BOOTLOADER_OFFSET, image_len);
|
||||||
if (!image) {
|
if (!image) {
|
||||||
ESP_LOGE(TAG, "bootloader_mmap(0x1000, 0x%x) failed", image_len);
|
ESP_LOGE(TAG, "bootloader_mmap(0x1000, 0x%x) failed", image_len);
|
||||||
return false;
|
return false;
|
||||||
|
@ -111,7 +111,7 @@ esp_err_t esp_secure_boot_permanently_enable(void) {
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = esp_image_basic_verify(0x1000, true, &image_len);
|
err = esp_image_verify_bootloader(&image_len);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "bootloader image appears invalid! error %d", err);
|
ESP_LOGE(TAG, "bootloader image appears invalid! error %d", err);
|
||||||
return err;
|
return err;
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include "sdkconfig.h"
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
#include "bootloader_flash.h"
|
#include "bootloader_flash.h"
|
||||||
|
#include "bootloader_sha.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_image_format.h"
|
#include "esp_image_format.h"
|
||||||
#include "esp_secure_boot.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 SIGNATURE_VERIFICATION_KEYLEN 64
|
||||||
|
|
||||||
|
#define DIGEST_LEN 32
|
||||||
|
|
||||||
esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length)
|
esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length)
|
||||||
{
|
{
|
||||||
#ifdef BOOTLOADER_BUILD
|
uint8_t digest[DIGEST_LEN];
|
||||||
SHA_CTX sha;
|
|
||||||
#endif
|
|
||||||
uint8_t digest[32];
|
|
||||||
ptrdiff_t keylen;
|
|
||||||
const uint8_t *data;
|
const uint8_t *data;
|
||||||
const esp_secure_boot_sig_block_t *sigblock;
|
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);
|
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;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
sigblock = (const esp_secure_boot_sig_block_t *)(data + length);
|
// Calculate digest of main image
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef BOOTLOADER_BUILD
|
#ifdef BOOTLOADER_BUILD
|
||||||
/* Use ROM SHA functions directly */
|
bootloader_sha256_handle_t handle = bootloader_sha256_start();
|
||||||
ets_sha_enable();
|
bootloader_sha256_data(handle, data, length);
|
||||||
ets_sha_init(&sha);
|
bootloader_sha256_finish(handle, digest);
|
||||||
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();
|
|
||||||
#else
|
#else
|
||||||
/* Use thread-safe esp-idf SHA function */
|
/* Use thread-safe esp-idf SHA function */
|
||||||
esp_sha(SHA2_256, data, length, digest);
|
esp_sha(SHA2_256, data, length, digest);
|
||||||
#endif
|
#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;
|
keylen = signature_verification_key_end - signature_verification_key_start;
|
||||||
if(keylen != SIGNATURE_VERIFICATION_KEYLEN) {
|
if(keylen != SIGNATURE_VERIFICATION_KEYLEN) {
|
||||||
ESP_LOGE(TAG, "Embedded public verification key has wrong length %d", keylen);
|
ESP_LOGE(TAG, "Embedded public verification key has wrong length %d", keylen);
|
||||||
goto unmap_and_fail;
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sig_block->version != 0) {
|
||||||
|
ESP_LOGE(TAG, "image has invalid signature version field 0x%08x", sig_block->version);
|
||||||
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
is_valid = uECC_verify(signature_verification_key_start,
|
is_valid = uECC_verify(signature_verification_key_start,
|
||||||
digest, sizeof(digest), sigblock->signature,
|
image_digest,
|
||||||
uECC_secp256r1());
|
DIGEST_LEN,
|
||||||
|
sig_block->signature,
|
||||||
bootloader_munmap(data);
|
uECC_secp256r1());
|
||||||
return is_valid ? ESP_OK : ESP_ERR_IMAGE_INVALID;
|
return is_valid ? ESP_OK : ESP_ERR_IMAGE_INVALID;
|
||||||
|
|
||||||
unmap_and_fail:
|
|
||||||
bootloader_munmap(data);
|
|
||||||
return ESP_FAIL;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Tests for bootloader_support esp_image_basic_verify()
|
* Tests for bootloader_support esp_load(ESP_IMAGE_VERIFY, ...)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <esp_types.h>
|
#include <esp_types.h>
|
||||||
|
@ -19,19 +19,31 @@
|
||||||
|
|
||||||
TEST_CASE("Verify bootloader image in flash", "[bootloader_support]")
|
TEST_CASE("Verify bootloader image in flash", "[bootloader_support]")
|
||||||
{
|
{
|
||||||
uint32_t image_len = 0;
|
const esp_partition_pos_t fake_bootloader_partition = {
|
||||||
TEST_ASSERT_EQUAL_HEX(ESP_OK, esp_image_basic_verify(0x1000, true, &image_len));
|
.offset = ESP_BOOTLOADER_OFFSET,
|
||||||
TEST_ASSERT_NOT_EQUAL(0, image_len);
|
.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]")
|
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();
|
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||||
TEST_ASSERT_NOT_EQUAL(NULL, running);
|
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_EQUAL_HEX(ESP_OK, esp_image_load(ESP_IMAGE_VERIFY, &running_pos, &data));
|
||||||
TEST_ASSERT_NOT_EQUAL(0, image_len);
|
TEST_ASSERT_NOT_EQUAL(0, data.image_len);
|
||||||
TEST_ASSERT_TRUE(image_len <= running->size);
|
TEST_ASSERT_TRUE(data.image_len <= running->size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 325f01637b667af02cc6390965b09d50e6a31dac
|
Subproject commit a4207741eca1fb8e5e3670e498ed058320bbcb5a
|
|
@ -4261,7 +4261,9 @@
|
||||||
/* Flash MMU table for APP CPU */
|
/* Flash MMU table for APP CPU */
|
||||||
#define DPORT_APP_FLASH_MMU_TABLE ((volatile uint32_t*) 0x3FF12000)
|
#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_ */
|
#endif /*_SOC_DPORT_REG_H_ */
|
||||||
|
|
||||||
|
|
|
@ -83,14 +83,14 @@ static void IRAM_ATTR spi_flash_mmap_init()
|
||||||
uint32_t entry_app = DPORT_APP_FLASH_MMU_TABLE[i];
|
uint32_t entry_app = DPORT_APP_FLASH_MMU_TABLE[i];
|
||||||
if (entry_pro != entry_app) {
|
if (entry_pro != entry_app) {
|
||||||
// clean up entries used by boot loader
|
// clean up entries used by boot loader
|
||||||
entry_pro = INVALID_ENTRY_VAL;
|
entry_pro = DPORT_FLASH_MMU_TABLE_INVALID_VAL;
|
||||||
DPORT_PRO_FLASH_MMU_TABLE[i] = INVALID_ENTRY_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)) {
|
if ((entry_pro & INVALID_ENTRY_VAL) == 0 && (i == 0 || i == PRO_IRAM0_FIRST_USABLE_PAGE || entry_pro != 0)) {
|
||||||
s_mmap_page_refcnt[i] = 1;
|
s_mmap_page_refcnt[i] = 1;
|
||||||
} else {
|
} else {
|
||||||
DPORT_PRO_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] = INVALID_ENTRY_VAL;
|
DPORT_APP_FLASH_MMU_TABLE[i] = DPORT_FLASH_MMU_TABLE_INVALID_VAL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -290,7 +290,7 @@ export HOSTCC HOSTLD HOSTAR HOSTOBJCOPY SIZE
|
||||||
CC := $(call dequote,$(CONFIG_TOOLPREFIX))gcc
|
CC := $(call dequote,$(CONFIG_TOOLPREFIX))gcc
|
||||||
CXX := $(call dequote,$(CONFIG_TOOLPREFIX))c++
|
CXX := $(call dequote,$(CONFIG_TOOLPREFIX))c++
|
||||||
LD := $(call dequote,$(CONFIG_TOOLPREFIX))ld
|
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
|
OBJCOPY := $(call dequote,$(CONFIG_TOOLPREFIX))objcopy
|
||||||
SIZE := $(call dequote,$(CONFIG_TOOLPREFIX))size
|
SIZE := $(call dequote,$(CONFIG_TOOLPREFIX))size
|
||||||
export CC CXX LD AR OBJCOPY SIZE
|
export CC CXX LD AR OBJCOPY SIZE
|
||||||
|
|
|
@ -19,6 +19,7 @@ CONFIG_LOG_BOOTLOADER_LEVEL_WARN=y
|
||||||
# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set
|
# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set
|
||||||
# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set
|
# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set
|
||||||
CONFIG_LOG_BOOTLOADER_LEVEL=2
|
CONFIG_LOG_BOOTLOADER_LEVEL=2
|
||||||
|
# CONFIG_BOOTLOADER_LTO is not set
|
||||||
|
|
||||||
#
|
#
|
||||||
# Security features
|
# Security features
|
||||||
|
|
Loading…
Reference in a new issue