bootloader: Combine loading from flash & verifying to save boot time

Still needs updating to account for secure boot.
This commit is contained in:
Angus Gratton 2017-06-16 16:30:21 +10:00
parent 105c4b7386
commit 0c8888d68f
8 changed files with 409 additions and 352 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)
{
ota_ops_entry_t *it;
size_t image_size;
esp_err_t ret = ESP_OK;
for (it = LIST_FIRST(&s_ota_ops_entries_head); it != NULL; it = LIST_NEXT(it, entries)) {
@ -230,13 +229,19 @@ esp_err_t esp_ota_end(esp_ota_handle_t handle)
it->partial_bytes = 0;
}
if (esp_image_basic_verify(it->part->address, true, &image_size) != ESP_OK) {
esp_image_metadata_t data;
const esp_partition_pos_t part_pos = {
.offset = it->part->address,
.size = it->part->size,
};
if (esp_image_load(ESP_IMAGE_VERIFY, &part_pos, &data) != ESP_OK) {
ret = ESP_ERR_OTA_VALIDATE_FAILED;
goto cleanup;
}
#ifdef CONFIG_SECURE_BOOT_ENABLED
ret = esp_secure_boot_verify_signature(it->part->address, image_size);
ret = esp_secure_boot_verify_signature(it->part->address, data.image_length);
if (ret != ESP_OK) {
ret = ESP_ERR_OTA_VALIDATE_FAILED;
goto cleanup;
@ -365,18 +370,22 @@ static esp_err_t esp_rewrite_ota_data(esp_partition_subtype_t subtype)
esp_err_t esp_ota_set_boot_partition(const esp_partition_t *partition)
{
size_t image_size;
const esp_partition_t *find_partition = NULL;
if (partition == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (esp_image_basic_verify(partition->address, true, &image_size) != ESP_OK) {
esp_image_metadata_t data;
const esp_partition_pos_t part_pos = {
.offset = partition->address,
.size = partition->size,
};
if (esp_image_load(ESP_IMAGE_VERIFY, &part_pos, &data) != ESP_OK) {
return ESP_ERR_OTA_VALIDATE_FAILED;
}
#ifdef CONFIG_SECURE_BOOT_ENABLED
esp_err_t ret = esp_secure_boot_verify_signature(partition->address, image_size);
esp_err_t ret = esp_secure_boot_verify_signature(partition->address, data.image_length);
if (ret != ESP_OK) {
return ESP_ERR_OTA_VALIDATE_FAILED;
}

View file

@ -53,17 +53,18 @@
extern int _bss_start;
extern int _bss_end;
extern int _data_start;
extern int _data_end;
static const char* TAG = "boot";
/*
We arrive here after the bootloader finished loading the program from flash. The hardware is mostly uninitialized,
flash cache is down and the app CPU is in reset. We do have a stack, so we can do the initialization in C.
*/
/* Reduce literal size for some generic string literals */
#define MAP_MSG "Mapping segment %d as %s"
#define MAP_ERR_MSG "Image contains multiple %s segments. Only the last one will be mapped."
void bootloader_main();
static void unpack_load_app(const esp_partition_pos_t *app_node);
void print_flash_info(const esp_image_header_t* pfhdr);
static void print_flash_info(const esp_image_header_t* pfhdr);
static void set_cache_and_start_app(uint32_t drom_addr,
uint32_t drom_load_addr,
uint32_t drom_size,
@ -76,10 +77,22 @@ static void clock_configure(void);
static void uart_console_configure(void);
static void wdt_reset_check(void);
void IRAM_ATTR call_start_cpu0()
/*
* We arrive here after the ROM bootloader finished loading this second stage bootloader from flash.
* The hardware is mostly uninitialized, flash cache is down and the app CPU is in reset.
* We do have a stack, so we can do the initialization in C.
*/
void call_start_cpu0()
{
cpu_configure_region_protection();
/* Sanity check that static RAM is after the stack */
int *sp = get_sp();
assert(&_bss_start <= &_bss_end);
assert(&_data_start <= &_data_end);
assert(sp < &_bss_start);
assert(sp < &_data_start);
//Clear bss
memset(&_bss_start, 0, (&_bss_end - &_bss_start) * sizeof(_bss_start));
@ -253,7 +266,7 @@ void bootloader_main()
memset(&bs, 0, sizeof(bs));
ESP_LOGI(TAG, "compile time " __TIME__ );
ets_set_appcpu_boot_addr(0);
ets_set_appcpu_boot_addr(0);
/* disable watch dog here */
REG_CLR_BIT( RTC_CNTL_WDTCONFIG0_REG, RTC_CNTL_WDT_FLASHBOOT_MOD_EN );
@ -276,7 +289,8 @@ void bootloader_main()
bootloader_enable_qio_mode();
#endif
if(esp_image_load_header(0x1000, true, &fhdr) != ESP_OK) {
if (bootloader_flash_read(ESP_BOOTLOADER_OFFSET, &fhdr,
sizeof(esp_image_header_t), true) != ESP_OK) {
ESP_LOGE(TAG, "failed to load bootloader header!");
return;
}
@ -408,11 +422,10 @@ void bootloader_main()
static void unpack_load_app(const esp_partition_pos_t* partition)
{
esp_err_t err;
esp_image_header_t image_header;
uint32_t image_length;
esp_image_metadata_t data;
/* TODO: verify the app image as part of OTA boot decision, so can have fallbacks */
err = esp_image_basic_verify(partition->offset, true, &image_length);
/* TODO: load the app image as part of OTA boot decision, so can fallback if loading fails */
err = esp_image_load(ESP_IMAGE_LOAD, partition, &data);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to verify app image @ 0x%x (%d)", partition->offset, err);
return;
@ -420,8 +433,8 @@ static void unpack_load_app(const esp_partition_pos_t* partition)
#ifdef CONFIG_SECURE_BOOT_ENABLED
if (esp_secure_boot_enabled()) {
ESP_LOGI(TAG, "Verifying app signature @ 0x%x (length 0x%x)", partition->offset, image_length);
err = esp_secure_boot_verify_signature(partition->offset, image_length);
ESP_LOGI(TAG, "Verifying app signature @ 0x%x (length 0x%x)", partition->offset, data.image_length);
err = esp_secure_boot_verify_signature(partition->offset, data.image_length);
if (err != ESP_OK) {
ESP_LOGE(TAG, "App image @ 0x%x failed signature verification (%d)", partition->offset, err);
return;
@ -430,11 +443,6 @@ static void unpack_load_app(const esp_partition_pos_t* partition)
}
#endif
if (esp_image_load_header(partition->offset, true, &image_header) != ESP_OK) {
ESP_LOGE(TAG, "Failed to load app image header @ 0x%x", partition->offset);
return;
}
uint32_t drom_addr = 0;
uint32_t drom_load_addr = 0;
uint32_t drom_size = 0;
@ -442,124 +450,39 @@ static void unpack_load_app(const esp_partition_pos_t* partition)
uint32_t irom_load_addr = 0;
uint32_t irom_size = 0;
/* Reload the RTC memory segments whenever a non-deepsleep reset
is occurring */
bool load_rtc_memory = rtc_get_reset_reason(0) != DEEPSLEEP_RESET;
ESP_LOGD(TAG, "bin_header: %u %u %u %u %08x", image_header.magic,
image_header.segment_count,
image_header.spi_mode,
image_header.spi_size,
(unsigned)image_header.entry_addr);
/* Important: From here on this function cannot access any global data (bss/data segments),
as loading the app image may overwrite these.
*/
for (int segment = 0; segment < image_header.segment_count; segment++) {
esp_image_segment_header_t segment_header;
uint32_t data_offs;
if(esp_image_load_segment_header(segment, partition->offset,
&image_header, true,
&segment_header, &data_offs) != ESP_OK) {
ESP_LOGE(TAG, "failed to load segment header #%d", segment);
return;
}
const uint32_t address = segment_header.load_addr;
bool load = true;
bool map = false;
if (address == 0x00000000) { // padding, ignore block
load = false;
}
if (address == 0x00000004) {
load = false; // md5 checksum block
// TODO: actually check md5
}
if (address >= SOC_DROM_LOW && address < SOC_DROM_HIGH) {
ESP_LOGD(TAG, "found drom segment, map from %08x to %08x", data_offs,
segment_header.load_addr);
drom_addr = data_offs;
drom_load_addr = segment_header.load_addr;
drom_size = segment_header.data_len + sizeof(segment_header);
load = false;
map = true;
}
if (address >= SOC_IROM_LOW && address < SOC_IROM_HIGH) {
ESP_LOGD(TAG, "found irom segment, map from %08x to %08x", data_offs,
segment_header.load_addr);
irom_addr = data_offs;
irom_load_addr = segment_header.load_addr;
irom_size = segment_header.data_len + sizeof(segment_header);
load = false;
map = true;
}
if (!load_rtc_memory && address >= SOC_RTC_IRAM_LOW && address < SOC_RTC_IRAM_HIGH) {
ESP_LOGD(TAG, "Skipping RTC code segment at %08x\n", data_offs);
load = false;
}
if (!load_rtc_memory && address >= SOC_RTC_DATA_LOW && address < SOC_RTC_DATA_HIGH) {
ESP_LOGD(TAG, "Skipping RTC data segment at %08x\n", data_offs);
load = false;
}
ESP_LOGI(TAG, "segment %d: paddr=0x%08x vaddr=0x%08x size=0x%05x (%6d) %s", segment, data_offs - sizeof(esp_image_segment_header_t),
segment_header.load_addr, segment_header.data_len, segment_header.data_len, (load)?"load":(map)?"map":"");
if (load) {
intptr_t sp, start_addr, end_addr;
ESP_LOGV(TAG, "bootloader_mmap data_offs=%08x data_len=%08x", data_offs, segment_header.data_len);
start_addr = segment_header.load_addr;
end_addr = start_addr + segment_header.data_len;
/* Before loading segment, check it doesn't clobber
bootloader RAM... */
if (end_addr < 0x40000000) {
if (end_addr > 0x3FFE0000) {
/* Temporary workaround for an ugly crash, until we allow >192KB of static DRAM */
ESP_LOGE(TAG, "DRAM segment %d (start 0x%08x end 0x%08x) too large for IDF to boot",
segment, start_addr, end_addr);
return;
}
sp = (intptr_t)get_sp();
if (end_addr > sp) {
ESP_LOGE(TAG, "Segment %d end address %08x overlaps bootloader stack %08x - can't load",
segment, end_addr, sp);
return;
}
if (end_addr > sp - 256) {
/* We don't know for sure this is the stack high water mark, so warn if
it seems like we may overflow.
*/
ESP_LOGW(TAG, "Segment %d end address %08x close to stack pointer %08x",
segment, end_addr, sp);
}
// Find DROM & IROM addresses, to configure cache mappings
for (int i = 0; i < data.image.segment_count; i++) {
esp_image_segment_header_t *header = &data.segments[i];
if (header->load_addr >= SOC_IROM_LOW && header->load_addr < SOC_IROM_HIGH) {
if (drom_addr != 0) {
ESP_LOGE(TAG, MAP_ERR_MSG, "DROM");
} else {
ESP_LOGD(TAG, "Mapping segment %d as %s", i, "DROM");
}
const void *data = bootloader_mmap(data_offs, segment_header.data_len);
if(!data) {
ESP_LOGE(TAG, "bootloader_mmap(0x%xc, 0x%x) failed",
data_offs, segment_header.data_len);
return;
drom_addr = data.segment_data[i];
drom_load_addr = header->load_addr;
drom_size = header->data_len;
}
if (header->load_addr >= SOC_DROM_LOW && header->load_addr < SOC_DROM_HIGH) {
if (irom_addr != 0) {
ESP_LOGE(TAG, MAP_ERR_MSG, "IROM");
} else {
ESP_LOGD(TAG, "Mapping segment %d as %s", i, "IROM");
}
memcpy((void *)segment_header.load_addr, data, segment_header.data_len);
bootloader_munmap(data);
irom_addr = data.segment_data[i];
irom_load_addr = header->load_addr;
irom_size = header->data_len;
}
}
ESP_LOGD(TAG, "calling set_cache_and_start_app");
set_cache_and_start_app(drom_addr,
drom_load_addr,
drom_size,
irom_addr,
irom_load_addr,
irom_size,
image_header.entry_addr);
data.image.entry_addr);
}
static void set_cache_and_start_app(
@ -632,7 +555,7 @@ static void update_flash_config(const esp_image_header_t* pfhdr)
Cache_Read_Enable( 0 );
}
void print_flash_info(const esp_image_header_t* phdr)
static void print_flash_info(const esp_image_header_t* phdr)
{
#if (BOOT_LOG_LEVEL >= BOOT_LOG_LEVEL_NOTICE)
@ -862,3 +785,9 @@ static void wdt_reset_check(void)
}
wdt_reset_cpu0_info_enable();
}
void __assert_func(const char *file, int line, const char *func, const char *expr)
{
ESP_LOGE(TAG, "Assert failed in %s, %s:%d (%s)", func, file, line, expr);
while(1) {}
}

View file

@ -1,23 +1,21 @@
/*
Linker file used to link the bootloader.
*WARNING* For now this linker dumps everything into IRAM/DRAM. ToDo: move
some/most stuff to DROM/IROM.
*/
/* THESE ARE THE VIRTUAL RUNTIME ADDRESSES */
/* The load addresses are defined later using the AT statements. */
/* Simplified memory map for the bootloader
The main purpose is to make sure the bootloader can load into main memory
without overwriting itself.
*/
MEMORY
{
/* All these values assume the flash cache is on, and have the blocks this uses subtracted from the length
of the various regions. The 'data access port' dram/drom regions map to the same iram/irom regions but
are connected to the data port of the CPU and eg allow bytewise access. */
dport0_seg (RW) : org = 0x3FF00000, len = 0x10 /* IO */
iram_seg (RWX) : org = 0x40080000, len = 0x400 /* 1k of IRAM used by bootloader functions which need to flush/enable APP CPU cache */
iram_pool_1_seg (RWX) : org = 0x40078000, len = 0x8000 /* IRAM POOL1, used for APP CPU cache. We can abuse it in bootloader because APP CPU is still held in reset, until we enable APP CPU cache */
dram_seg (RW) : org = 0x3FFF0000, len = 0x10000 /* 64k at the end of DRAM, after ROM bootloader stack */
/* I/O */
dport0_seg (RW) : org = 0x3FF00000, len = 0x10
/* IRAM POOL1, used for APP CPU cache. We can abuse it in bootloader because APP CPU is still held in reset, the main app enables APP CPU cache */
iram_seg (RWX) : org = 0x40078000, len = 0x8000
/* 64k at the end of DRAM, after ROM bootloader stack */
dram_seg (RW) : org = 0x3FFF0000, len = 0x10000
}
/* Default entry point: */
@ -28,19 +26,10 @@ SECTIONS
{
.iram1.text :
{
_init_start = ABSOLUTE(.);
*(.UserEnter.literal);
*(.UserEnter.text);
. = ALIGN (16);
*(.entry.text)
*(.init.literal)
*(.init)
_init_end = ABSOLUTE(.);
/* Code marked as runnning out of IRAM */
_iram_text_start = ABSOLUTE(.);
*(.iram1 .iram1.*)
_iram_text_end = ABSOLUTE(.);
} > iram_seg
@ -66,7 +55,6 @@ SECTIONS
_bss_end = ABSOLUTE(.);
} >dram_seg
.dram0.data :
{
_data_start = ABSOLUTE(.);
@ -84,15 +72,11 @@ SECTIONS
_data_end = ABSOLUTE(.);
} >dram_seg
.dram0.rodata :
{
_rodata_start = ABSOLUTE(.);
*(.rodata)
*(.rodata.*)
*(.irom1.text) /* catch stray ICACHE_RODATA_ATTR */
*(.gnu.linkonce.r.*)
*(.rodata1)
__XT_EXCEPTION_TABLE_ = ABSOLUTE(.);
@ -132,17 +116,17 @@ SECTIONS
_heap_start = ABSOLUTE(.);
} >dram_seg
.iram_pool_1.text :
.iram.text :
{
_stext = .;
_text_start = ABSOLUTE(.);
*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)
*(.irom0.text) /* catch stray ICACHE_RODATA_ATTR */
*(.iram1 .iram1.*) /* catch stray IRAM_ATTR */
*(.fini.literal)
*(.fini)
*(.gnu.version)
_text_end = ABSOLUTE(.);
_etext = .;
} >iram_pool_1_seg
} > iram_seg
}

View file

@ -11,11 +11,11 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef __ESP32_IMAGE_FORMAT_H
#define __ESP32_IMAGE_FORMAT_H
#pragma once
#include <stdbool.h>
#include <esp_err.h>
#include "esp_flash_partitions.h"
#define ESP_ERR_IMAGE_BASE 0x2000
#define ESP_ERR_IMAGE_FLASH_FAIL (ESP_ERR_IMAGE_BASE + 1)
@ -73,62 +73,56 @@ typedef struct {
uint32_t data_len;
} esp_image_segment_header_t;
#define ESP_IMAGE_MAX_SEGMENTS 16
/* Structure to hold on-flash image metadata */
typedef struct {
uint32_t start_addr; /* Start address of image */
esp_image_header_t image; /* Header for entire image */
esp_image_segment_header_t segments[ESP_IMAGE_MAX_SEGMENTS]; /* Per-segment header data */
uint32_t segment_data[ESP_IMAGE_MAX_SEGMENTS]; /* Data offsets for each segment */
uint32_t image_length;
} 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.
*
* @param src_addr Address in flash to load image header. Must be 4 byte aligned.
* @param log_errors Log error output if image header appears invalid.
* @param[out] image_header Pointer to an esp_image_header_t struture to be filled with data. If the function fails, contents are undefined.
*
* @return ESP_OK if image header was loaded, ESP_ERR_IMAGE_FLASH_FAIL
* if a SPI flash error occurs, ESP_ERR_IMAGE_INVALID if the image header
* appears invalid.
*/
esp_err_t esp_image_load_header(uint32_t src_addr, bool log_errors, esp_image_header_t *image_header);
/**
* @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.
* @param part Partition to load the app from.
* @param[out] data Pointer to the metadata structure to be filled in by this function. 'start_addr' member should be set (to the start address of the image.) Other fields will all be initialised by this function.
* @param log_errors Log errors reading the image.
*
* Image validation checks:
* - Magic byte
* - No single segment longer than 16MB
* - Total image no longer than 16MB
* - Partition smaller than 16MB
* - All segments & image fit in partition
* - 8 bit image checksum is valid
* - (Signature) if signature verification is enabled
*
* If flash encryption is enabled, the image will be tranpsarently decrypted.
*
* @param src_addr Offset of the start of the image in flash. Must be 4 byte aligned.
* @param allow_decrypt If true and flash encryption is enabled, the image will be transparently decrypted.
* @param log_errors Log errors verifying the image.
* @param[out] length Length of the image, set to a value if the image is valid. Can be null.
*
* @return ESP_OK if image is valid, ESP_FAIL or ESP_ERR_IMAGE_INVALID on errors.
*
* @return ESP_OK if image metadata was loaded successfully, ESP_ERR_IMAGE_FLASH_FAIL if a SPI flash error occurs, ESP_ERR_IMAGE_INVALID if the image appears invalid, ESP_ERR_INVALID_ARG if the data pointer is invalid.
*/
esp_err_t esp_image_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 {
@ -139,5 +133,3 @@ typedef struct {
uint32_t irom_load_addr;
uint32_t irom_size;
} esp_image_flash_mapping_t;
#endif

View file

@ -88,6 +88,7 @@ static const char *TAG = "bootloader_flash";
static bool mapped;
// Current bootloader mapping (ab)used for bootloader_read()
static uint32_t current_read_mapping = UINT32_MAX;
const void *bootloader_mmap(uint32_t src_addr, uint32_t size)

View file

@ -13,6 +13,8 @@
// limitations under the License.
#include <string.h>
#include <rom/rtc.h>
#include <soc/cpu.h>
#include <esp_image_format.h>
#include <esp_log.h>
#include <bootloader_flash.h>
@ -22,168 +24,314 @@ static const char *TAG = "esp_image";
#define SIXTEEN_MB 0x1000000
#define ESP_ROM_CHECKSUM_INITIAL 0xEF
esp_err_t esp_image_load_header(uint32_t src_addr, bool log_errors, esp_image_header_t *image_header)
{
esp_err_t err;
ESP_LOGD(TAG, "reading image header @ 0x%x", src_addr);
err = bootloader_flash_read(src_addr, image_header, sizeof(esp_image_header_t), true);
if (err == ESP_OK) {
if (image_header->magic != ESP_IMAGE_HEADER_MAGIC) {
if (log_errors) {
ESP_LOGE(TAG, "image at 0x%x has invalid magic byte", src_addr);
}
err = ESP_ERR_IMAGE_INVALID;
}
if (log_errors) {
if (image_header->spi_mode > ESP_IMAGE_SPI_MODE_SLOW_READ) {
ESP_LOGW(TAG, "image at 0x%x has invalid SPI mode %d", src_addr, image_header->spi_mode);
}
if (image_header->spi_speed > ESP_IMAGE_SPI_SPEED_80M) {
ESP_LOGW(TAG, "image at 0x%x has invalid SPI speed %d", src_addr, image_header->spi_speed);
}
if (image_header->spi_size > ESP_IMAGE_FLASH_SIZE_MAX) {
ESP_LOGW(TAG, "image at 0x%x has invalid SPI size %d", src_addr, image_header->spi_size);
}
}
}
if (err != ESP_OK) {
bzero(image_header, sizeof(esp_image_header_t));
}
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)
/* Headroom to ensure between stack SP (at time of checking) and data loaded from flash */
#define STACK_LOAD_HEADROOM 4096
/* Return true if load_addr is an address the bootloader should load into */
static bool should_load(uint32_t load_addr);
/* Return true if load_addr is an address the bootloader should map via flash cache */
static bool should_map(uint32_t load_addr);
/* Load or verify a segment */
static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segment_header_t *header, bool silent, bool do_load, uint32_t *checksum);
/* Verify the main image header */
static esp_err_t verify_image_header(uint32_t src_addr, const esp_image_header_t *image, bool silent);
/* Verify a segment header */
static esp_err_t verify_segment_header(int index, const esp_image_segment_header_t *segment, uint32_t segment_data_offs, bool silent);
/* Log-and-fail macro for use in esp_image_load */
#define FAIL_LOAD(...) do { \
if (!silent) { \
ESP_LOGE(TAG, __VA_ARGS__); \
} \
goto err; \
} \
while(0)
esp_err_t esp_image_load(esp_image_load_mode_t mode, const esp_partition_pos_t *part, esp_image_metadata_t *data)
{
#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;
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;
if(index >= image_header->segment_count) {
if (log_errors) {
ESP_LOGE(TAG, "index %d higher than segment count %d", index, image_header->segment_count);
}
if (data == NULL || part == NULL) {
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 (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) {
*segment_data_offset = 0;
bzero(segment_header, sizeof(esp_image_segment_header_t));
goto err;
}
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 && err == ESP_OK; i++) {
esp_image_segment_header_t *header = &data->segments[i];
ESP_LOGV(TAG, "loading segment header %d at offset 0x%x", i, next_addr);
err = process_segment(i, next_addr, header, silent, do_load, &checksum_word);
if (err != ESP_OK) {
goto err;
}
next_addr += sizeof(esp_image_segment_header_t);
data->segment_data[i] = next_addr;
next_addr += header->data_len;
}
// Segments all loaded, verify length
uint32_t end_addr = next_addr;
if (end_addr < data->start_addr) {
FAIL_LOAD("image offset has wrapped");
}
uint32_t length = end_addr - data->start_addr;
length = length + 1; // Add a byte for the checksum
length = (length + 15) & ~15; // Pad to next full 16 byte block
if (length > part->size) {
FAIL_LOAD("Image length %d doesn't fit in partition length %d", length, part->size);
}
// Verify checksum
uint32_t buf[16/sizeof(uint32_t)];
err = bootloader_flash_read(data->start_addr + length - 16, buf, 16, true);
uint8_t calc = ((uint8_t *)buf)[15];
uint8_t checksum = (checksum_word >> 24)
^ (checksum_word >> 16)
^ (checksum_word >> 8)
^ (checksum_word >> 0);
if (err != ESP_OK || checksum != calc) {
FAIL_LOAD("Checksum failed. Calculated 0x%x read 0x%x",
checksum, calc);
}
data->image_length = length;
// Success!
return ESP_OK;
err:
if (err == ESP_OK) {
err = ESP_ERR_IMAGE_INVALID;
}
// Prevent invalid/incomplete data leaking out
bzero(data, sizeof(esp_image_metadata_t));
return err;
}
esp_err_t esp_image_basic_verify(uint32_t src_addr, bool log_errors, uint32_t *p_length)
static esp_err_t verify_image_header(uint32_t src_addr, const esp_image_header_t *image, bool silent)
{
esp_err_t err = 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, uint32_t *checksum)
{
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) {
*p_length = 0;
}
err = esp_image_load_header(src_addr, log_errors, &image_header);
/* read segment header */
err = bootloader_flash_read(flash_addr, header, sizeof(esp_image_segment_header_t), true);
if (err != ESP_OK) {
return err;
}
ESP_LOGD(TAG, "reading %d image segments", image_header.segment_count);
intptr_t load_addr = header->load_addr;
uint32_t data_len = header->data_len;
uint32_t data_addr = flash_addr + sizeof(esp_image_segment_header_t);
/* Checksum each segment's data */
for (int i = 0; i < image_header.segment_count; i++) {
err = esp_image_load_segment_header(i, src_addr, &image_header, log_errors,
&segment_header, &segment_data_offs);
if (err != ESP_OK) {
return err;
}
ESP_LOGV(TAG, "segment data length 0x%x data starts 0x%x", data_len, data_addr);
uint32_t load_addr = segment_header.load_addr;
bool map_segment = (load_addr >= SOC_DROM_LOW && load_addr < SOC_DROM_HIGH)
|| (load_addr >= SOC_IROM_LOW && load_addr < SOC_IROM_HIGH);
err = verify_segment_header(index, header, data_addr, silent);
if (err != ESP_OK) {
return err;
}
if (data_len % 4 != 0) {
FAIL_LOAD("unaligned segment length 0x%x", data_len);
}
/* Check that flash cache mapped segment aligns correctly from flash it's mapped address,
relative to the 64KB page mapping size.
*/
ESP_LOGV(TAG, "segment %d map_segment %d segment_data_offs 0x%x load_addr 0x%x",
i, map_segment, segment_data_offs, load_addr);
if (map_segment && ((segment_data_offs % SPI_FLASH_MMU_PAGE_SIZE) != (load_addr % SPI_FLASH_MMU_PAGE_SIZE))) {
ESP_LOGE(TAG, "Segment %d has load address 0x%08x, conflict with segment data at 0x%08x",
i, load_addr, segment_data_offs);
}
bool is_mapping = should_map(load_addr);
do_load = do_load && should_load(load_addr);
for (int i = 0; i < segment_header.data_len; i += sizeof(buf)) {
err = bootloader_flash_read(segment_data_offs + i, buf, sizeof(buf), true);
if (err != ESP_OK) {
return err;
}
for (int j = 0; j < sizeof(buf) && i + j < segment_header.data_len; j++) {
checksum ^= buf[j];
if (!silent) {
ESP_LOGI(TAG, "segment %d: paddr=0x%08x vaddr=0x%08x size=0x%05x (%6d) %s",
index, data_addr, load_addr,
data_len, data_len,
(do_load)?"load":(is_mapping)?"map":"");
}
if (do_load) {
/* Before loading segment, check it doesn't clobber bootloader RAM... */
uint32_t end_addr = load_addr + data_len;
if (end_addr < 0x40000000) {
intptr_t sp = (intptr_t)get_sp();
if (end_addr > sp - STACK_LOAD_HEADROOM) {
ESP_LOGE(TAG, "Segment %d end address 0x%08x too high (bootloader stack 0x%08x liimit 0x%08x)",
index, end_addr, sp, sp - STACK_LOAD_HEADROOM);
return ESP_ERR_IMAGE_INVALID;
}
}
}
/* End of image, verify checksum */
end_addr = segment_data_offs + segment_header.data_len;
const void *data = bootloader_mmap(data_addr, data_len);
if(!data) {
ESP_LOGE(TAG, "bootloader_mmap(0x%x, 0x%x) failed",
data_addr, data_len);
return ESP_FAIL;
}
const uint32_t *checksum_from;
if (do_load) {
memcpy((void *)load_addr, data, data_len);
checksum_from = (const uint32_t *)load_addr;
} else {
checksum_from = (const uint32_t *)data;
}
// Update checksum, either from RAM we just loaded or from flash
for (const uint32_t *c = checksum_from;
c < checksum_from + (data_len/sizeof(uint32_t));
c++) {
*checksum ^= *c;
}
if (end_addr < src_addr) {
if (log_errors) {
ESP_LOGE(TAG, "image offset has wrapped");
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;
}
length = end_addr - src_addr;
if (length >= SIXTEEN_MB) {
if (log_errors) {
ESP_LOGE(TAG, "invalid total length 0x%x", length);
uint32_t load_addr = segment->load_addr;
/* Check that flash cache mapped segment aligns correctly from flash to its mapped address,
relative to the 64KB page mapping size.
*/
ESP_LOGV(TAG, "segment %d map_segment %d segment_data_offs 0x%x load_addr 0x%x",
index, map_segment, segment_data_offs, load_addr);
if (should_map(load_addr)
&& ((segment_data_offs % SPI_FLASH_MMU_PAGE_SIZE) != (load_addr % SPI_FLASH_MMU_PAGE_SIZE))) {
if (!silent) {
ESP_LOGE(TAG, "Segment %d has load address 0x%08x, doesn't match segment data at 0x%08x",
index, load_addr, segment_data_offs);
}
return ESP_ERR_IMAGE_INVALID;
}
/* image padded to next full 16 byte block, with checksum byte at very end */
ESP_LOGV(TAG, "unpadded image length 0x%x", length);
length += 16; /* always pad by at least 1 byte */
length = length - (length % 16);
ESP_LOGV(TAG, "padded image length 0x%x", length);
ESP_LOGD(TAG, "reading checksum block at 0x%x", src_addr + length - 16);
bootloader_flash_read(src_addr + length - 16, buf, 16, true);
if (checksum != buf[15]) {
if (log_errors) {
ESP_LOGE(TAG, "checksum failed. Calculated 0x%x read 0x%x",
checksum, buf[15]);
}
return ESP_ERR_IMAGE_INVALID;
}
if (p_length != NULL) {
*p_length = length;
}
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_length : 0;
}
return err;
}

View file

@ -210,8 +210,8 @@ static esp_err_t encrypt_bootloader()
{
esp_err_t err;
uint32_t image_length;
/* Check for plaintext bootloader */
if (esp_image_basic_verify(ESP_BOOTLOADER_OFFSET, false, &image_length) == ESP_OK) {
/* Check for plaintext bootloader (verification will fail if it's already encrypted) */
if (esp_image_verify_bootloader(&image_length) == ESP_OK) {
ESP_LOGD(TAG, "bootloader is plaintext. Encrypting...");
err = esp_flash_encrypt_region(ESP_BOOTLOADER_OFFSET, image_length);
if (err != ESP_OK) {
@ -270,21 +270,15 @@ static esp_err_t encrypt_and_load_partition_table(esp_partition_info_t *partitio
static esp_err_t encrypt_partition(int index, const esp_partition_info_t *partition)
{
esp_err_t err;
uint32_t image_len = partition->pos.size;
bool should_encrypt = (partition->flags & PART_FLAG_ENCRYPTED);
if (partition->type == PART_TYPE_APP) {
/* check if the partition holds an unencrypted app */
if (esp_image_basic_verify(partition->pos.offset, false, &image_len) == ESP_OK) {
if(image_len > partition->pos.size) {
ESP_LOGE(TAG, "partition entry %d has image longer than partition (%d vs %d)", index, image_len, partition->pos.size);
should_encrypt = false;
} else {
should_encrypt = true;
}
} else {
should_encrypt = false;
}
/* check if the partition holds a valid unencrypted app */
esp_image_metadata_t data_ignored;
err = esp_image_load(ESP_IMAGE_VERIFY,
&partition->pos,
&data_ignored);
should_encrypt = (err == ESP_OK);
} else if (partition->type == PART_TYPE_DATA && partition->subtype == PART_SUBTYPE_DATA_OTA) {
/* check if we have ota data partition and the partition should be encrypted unconditionally */
should_encrypt = true;

View file

@ -67,7 +67,7 @@ static bool secure_boot_generate(uint32_t image_len){
}
/* generate digest from image contents */
image = bootloader_mmap(0x1000, image_len);
image = bootloader_mmap(ESP_BOOTLOADER_OFFSET, image_len);
if (!image) {
ESP_LOGE(TAG, "bootloader_mmap(0x1000, 0x%x) failed", image_len);
return false;
@ -111,7 +111,7 @@ esp_err_t esp_secure_boot_permanently_enable(void) {
return ESP_OK;
}
err = esp_image_basic_verify(0x1000, true, &image_len);
err = esp_image_verify_bootloader(&image_len);
if (err != ESP_OK) {
ESP_LOGE(TAG, "bootloader image appears invalid! error %d", err);
return err;