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:
Angus Gratton 2017-07-20 10:00:19 +08:00
commit e468cdee1d
20 changed files with 946 additions and 440 deletions

View file

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

View file

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

View file

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

View file

@ -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));
@ -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;
} }
drom_addr = data.segment_data[i];
const uint32_t address = segment_header.load_addr; drom_load_addr = header->load_addr;
bool load = true; drom_size = header->data_len;
bool map = false;
if (address == 0x00000000) { // padding, ignore block
load = false;
} }
if (address == 0x00000004) { if (header->load_addr >= SOC_DROM_LOW && header->load_addr < SOC_DROM_HIGH) {
load = false; // md5 checksum block if (irom_addr != 0) {
// TODO: actually check md5 ESP_LOGE(TAG, MAP_ERR_MSG, "IROM");
} else {
ESP_LOGD(TAG, "Mapping segment %d as %s", i, "IROM");
} }
irom_addr = data.segment_data[i];
if (address >= SOC_DROM_LOW && address < SOC_DROM_HIGH) { irom_load_addr = header->load_addr;
ESP_LOGD(TAG, "found drom segment, map from %08x to %08x", data_offs, irom_size = header->data_len;
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);
}
}
const void *data = bootloader_mmap(data_offs, segment_header.data_len);
if(!data) {
ESP_LOGE(TAG, "bootloader_mmap(0x%xc, 0x%x) failed",
data_offs, segment_header.data_len);
return;
}
memcpy((void *)segment_header.load_addr, data, segment_header.data_len);
bootloader_munmap(data);
} }
} }
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) {}
}

View file

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

View file

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

View file

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

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

View file

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

View 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

View file

@ -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
#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);
/* 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(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)
{ {
esp_err_t err; #ifdef BOOTLOADER_BUILD
ESP_LOGD(TAG, "reading image header @ 0x%x", src_addr); 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;
err = bootloader_flash_read(src_addr, image_header, sizeof(esp_image_header_t), true); if (data == NULL || part == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (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);
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);
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
#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) { if (err == ESP_OK) {
if (image_header->magic != ESP_IMAGE_HEADER_MAGIC) { err = ESP_ERR_IMAGE_INVALID;
if (log_errors) { }
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); ESP_LOGE(TAG, "image at 0x%x has invalid magic byte", src_addr);
} }
err = ESP_ERR_IMAGE_INVALID; err = ESP_ERR_IMAGE_INVALID;
} }
if (log_errors) { if (!silent) {
if (image_header->spi_mode > ESP_IMAGE_SPI_MODE_SLOW_READ) { 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_header->spi_mode); ESP_LOGW(TAG, "image at 0x%x has invalid SPI mode %d", src_addr, image->spi_mode);
} }
if (image_header->spi_speed > ESP_IMAGE_SPI_SPEED_80M) { if (image->spi_speed > ESP_IMAGE_SPI_SPEED_80M) {
ESP_LOGW(TAG, "image at 0x%x has invalid SPI speed %d", src_addr, image_header->spi_speed); ESP_LOGW(TAG, "image at 0x%x has invalid SPI speed %d", src_addr, image->spi_speed);
} }
if (image_header->spi_size > ESP_IMAGE_FLASH_SIZE_MAX) { if (image->spi_size > ESP_IMAGE_FLASH_SIZE_MAX) {
ESP_LOGW(TAG, "image at 0x%x has invalid SPI size %d", src_addr, image_header->spi_size); ESP_LOGW(TAG, "image at 0x%x has invalid SPI size %d", src_addr, image->spi_size);
} }
} }
}
if (err != ESP_OK) {
bzero(image_header, sizeof(esp_image_header_t));
}
return err; return err;
} }
esp_err_t esp_image_load_segment_header(uint8_t index, uint32_t src_addr, const esp_image_header_t *image_header, bool log_errors, esp_image_segment_header_t *segment_header, uint32_t *segment_data_offset) 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_OK;
uint32_t next_addr = src_addr + sizeof(esp_image_header_t);
if(index >= image_header->segment_count) {
if (log_errors) {
ESP_LOGE(TAG, "index %d higher than segment count %d", index, image_header->segment_count);
}
return ESP_ERR_INVALID_ARG;
}
for(int i = 0; i <= index && err == ESP_OK; i++) {
ESP_LOGV(TAG, "loading segment header %d at offset 0x%x", i, next_addr);
err = bootloader_flash_read(next_addr, segment_header, sizeof(esp_image_segment_header_t), true);
if (err == ESP_OK) {
if ((segment_header->data_len & 3) != 0
|| segment_header->data_len >= SIXTEEN_MB) {
if (log_errors) {
ESP_LOGE(TAG, "invalid segment length 0x%x", segment_header->data_len);
}
err = ESP_ERR_IMAGE_INVALID;
}
next_addr += sizeof(esp_image_segment_header_t);
ESP_LOGV(TAG, "segment data length 0x%x data starts 0x%x", segment_header->data_len, next_addr);
*segment_data_offset = next_addr;
next_addr += segment_header->data_len;
}
}
if (err != ESP_OK) {
*segment_data_offset = 0;
bzero(segment_header, sizeof(esp_image_segment_header_t));
}
return err;
}
esp_err_t esp_image_basic_verify(uint32_t src_addr, bool log_errors, uint32_t *p_length)
{ {
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 */
for (int i = 0; i < image_header.segment_count; i++) {
err = esp_image_load_segment_header(i, src_addr, &image_header, log_errors,
&segment_header, &segment_data_offs);
if (err != ESP_OK) {
return err;
} }
uint32_t load_addr = segment_header.load_addr; bool is_mapping = should_map(load_addr);
bool map_segment = (load_addr >= SOC_DROM_LOW && load_addr < SOC_DROM_HIGH) do_load = do_load && should_load(load_addr);
|| (load_addr >= SOC_IROM_LOW && load_addr < SOC_IROM_HIGH);
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":"");
}
/* Check that flash cache mapped segment aligns correctly from flash it's mapped address, 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;
}
}
}
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));
}
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;
}
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. relative to the 64KB page mapping size.
*/ */
ESP_LOGV(TAG, "segment %d map_segment %d segment_data_offs 0x%x load_addr 0x%x", ESP_LOGV(TAG, "segment %d map_segment %d segment_data_offs 0x%x load_addr 0x%x",
i, map_segment, segment_data_offs, load_addr); 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 (map_segment
ESP_LOGE(TAG, "Segment %d has load address 0x%08x, conflict with segment data at 0x%08x", && ((segment_data_offs % SPI_FLASH_MMU_PAGE_SIZE) != (load_addr % SPI_FLASH_MMU_PAGE_SIZE))) {
i, load_addr, segment_data_offs); if (!silent) {
} ESP_LOGE(TAG, "Segment %d load address 0x%08x, doesn't match data 0x%08x",
index, load_addr, segment_data_offs);
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 */
end_addr = segment_data_offs + segment_header.data_len;
if (end_addr < src_addr) {
if (log_errors) {
ESP_LOGE(TAG, "image offset has wrapped");
} }
return ESP_ERR_IMAGE_INVALID; return ESP_ERR_IMAGE_INVALID;
} }
length = end_addr - src_addr;
if (length >= SIXTEEN_MB) {
if (log_errors) {
ESP_LOGE(TAG, "invalid total length 0x%x", length);
}
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
}

View file

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

View file

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

View file

@ -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,
DIGEST_LEN,
sig_block->signature,
uECC_secp256r1()); uECC_secp256r1());
bootloader_munmap(data);
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;
} }

View file

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

View file

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

View file

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

View file

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

View file

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