fix(bootloader_support): Fix app verification when split across multiple mmaps

Merges https://github.com/espressif/esp-idf/pull/18495
This commit is contained in:
Nebojša Cvetković
2026-01-23 16:17:20 +00:00
committed by Konstantin Kondrashov
parent 67d1536ec2
commit 86d1448467
3 changed files with 127 additions and 27 deletions

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -78,8 +78,20 @@ static esp_err_t process_segments(esp_image_metadata_t *data, bool silent, bool
/* 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, esp_image_metadata_t *metadata);
typedef struct {
int segment;
intptr_t load_addr;
uint32_t data_addr;
uint32_t data_len;
bool do_load;
bool is_segment_start;
bootloader_sha256_handle_t sha_handle;
uint32_t *checksum;
esp_image_metadata_t *metadata;
} process_segment_data_t;
/* split segment and verify if data_len is too long */
static esp_err_t process_segment_data(int segment, intptr_t load_addr, uint32_t data_addr, uint32_t data_len, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum, esp_image_metadata_t *metadata);
static esp_err_t process_segment_data(const process_segment_data_t *segment_data);
/* Verify the main image header */
static esp_err_t verify_image_header(uint32_t src_addr, const esp_image_header_t *image, bool silent);
@@ -650,13 +662,24 @@ static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segme
uint32_t free_page_count = bootloader_mmap_get_free_pages();
ESP_LOGD(TAG, "free data page_count 0x%08"PRIx32, free_page_count);
process_segment_data_t segment_data = {
.segment = index,
.load_addr = load_addr,
.data_addr = data_addr,
.do_load = do_load,
.is_segment_start = true,
.sha_handle = sha_handle,
.checksum = checksum,
.metadata = metadata,
};
uint32_t data_len_remain = data_len;
while (data_len_remain > 0) {
#if (SECURE_BOOT_CHECK_SIGNATURE == 1) && defined(BOOTLOADER_BUILD)
/* Double check the address verification done above */
ESP_FAULT_ASSERT(!do_load || verify_load_addresses(0, load_addr, load_addr + data_len_remain, false, false));
ESP_FAULT_ASSERT(!do_load || verify_load_addresses(0, segment_data.load_addr,
segment_data.load_addr + data_len_remain, false, false));
#endif
uint32_t offset_page = ((data_addr & MMAP_ALIGNED_MASK) != 0) ? 1 : 0;
uint32_t offset_page = ((segment_data.data_addr & MMAP_ALIGNED_MASK) != 0) ? 1 : 0;
/* Data we could map in case we are not aligned to PAGE boundary is one page size lesser. */
uint32_t max_pages = (free_page_count > offset_page) ? (free_page_count - offset_page) : 0;
if (max_pages == 0) {
@@ -667,10 +690,12 @@ static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segme
if (__builtin_mul_overflow(max_pages, SPI_FLASH_MMU_PAGE_SIZE, &max_image_len)) {
max_image_len = UINT32_MAX;
}
data_len = MIN(data_len_remain, max_image_len);
CHECK_ERR(process_segment_data(index, load_addr, data_addr, data_len, do_load, sha_handle, checksum, metadata));
data_addr += data_len;
data_len_remain -= data_len;
segment_data.data_len = MIN(data_len_remain, max_image_len);
CHECK_ERR(process_segment_data(&segment_data));
segment_data.data_addr += segment_data.data_len;
segment_data.load_addr += segment_data.data_len;
data_len_remain -= segment_data.data_len;
segment_data.is_segment_start = false;
}
return ESP_OK;
@@ -718,26 +743,27 @@ static size_t process_esp_app_desc_data(const uint32_t *src, bootloader_sha256_h
}
#endif // CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK
static esp_err_t process_segment_data(int segment, intptr_t load_addr, uint32_t data_addr, uint32_t data_len, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum, esp_image_metadata_t *metadata)
static esp_err_t process_segment_data(const process_segment_data_t *segment_data)
{
// If we are not loading, and the checksum is empty, skip processing this
// segment for data
if (!do_load && checksum == NULL) {
if (!segment_data->do_load && segment_data->checksum == NULL) {
ESP_LOGD(TAG, "skipping checksum for segment");
return ESP_OK;
}
const uint32_t *data = (const uint32_t *)bootloader_mmap(data_addr, data_len);
const uint32_t *data = (const uint32_t *)bootloader_mmap(segment_data->data_addr,
segment_data->data_len);
if (!data) {
ESP_LOGE(TAG, "bootloader_mmap(0x%"PRIx32", 0x%"PRIx32") failed",
data_addr, data_len);
segment_data->data_addr, segment_data->data_len);
return ESP_FAIL;
}
if (checksum == NULL && sha_handle == NULL) {
memcpy((void *)load_addr, data, data_len);
if (segment_data->checksum == NULL && segment_data->sha_handle == NULL) {
memcpy((void *)segment_data->load_addr, data, segment_data->data_len);
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
if (esp_ptr_in_iram((uint32_t *)load_addr)) {
if (esp_ptr_in_iram((uint32_t *)segment_data->load_addr)) {
/* If we have manipulated data over dcache that will be read over icache then we need
to writeback, else the data read might be invalid */
cache_ll_writeback_all(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA, CACHE_LL_ID_ALL);
@@ -757,28 +783,32 @@ static esp_err_t process_segment_data(int segment, intptr_t load_addr, uint32_t
ram_obfs_value[1] ^= 0x66;
#endif
}
uint32_t *dest = (uint32_t *)load_addr;
uint32_t *dest = (uint32_t *)segment_data->load_addr;
#endif // BOOTLOADER_BUILD
const uint32_t *src = data;
uint32_t data_len = segment_data->data_len;
// Case I: Bootloader verifying application
// Case II: Bootloader verifying bootloader
// The esp_app_desc_t structure is located in DROM and is always in segment #0.
// Anti-rollback check and efuse block version check should handle only Case I from above.
if (segment == 0 && !is_bootloader(metadata->start_addr)) {
if (segment_data->segment == 0 && segment_data->is_segment_start &&
!is_bootloader(segment_data->metadata->start_addr)) {
/* ESP32 doesn't have more memory and more efuse bits for block major version. */
#if !CONFIG_IDF_TARGET_ESP32
const esp_app_desc_t *app_desc = (const esp_app_desc_t *)src;
esp_err_t ret = bootloader_common_check_efuse_blk_validity(app_desc->min_efuse_blk_rev_full, app_desc->max_efuse_blk_rev_full);
esp_err_t ret = bootloader_common_check_efuse_blk_validity(app_desc->min_efuse_blk_rev_full,
app_desc->max_efuse_blk_rev_full);
if (ret != ESP_OK) {
bootloader_munmap(data);
return ret;
}
#endif // !CONFIG_IDF_TARGET_ESP32
#if CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK
ESP_LOGD(TAG, "additional anti-rollback check 0x%"PRIx32, data_addr);
size_t len = process_esp_app_desc_data(src, sha_handle, checksum, metadata);
ESP_LOGD(TAG, "additional anti-rollback check 0x%"PRIx32, segment_data->data_addr);
size_t len = process_esp_app_desc_data(src, segment_data->sha_handle,
segment_data->checksum, segment_data->metadata);
data_len -= len;
src += len / 4;
// In BOOTLOADER_BUILD, for DROM (segment #0) we do not load it into dest (only map it), do_load = false.
@@ -788,11 +818,11 @@ static esp_err_t process_segment_data(int segment, intptr_t load_addr, uint32_t
for (size_t i = 0; i < data_len; i += 4) {
int w_i = i / 4; // Word index
uint32_t w = src[w_i];
if (checksum != NULL) {
*checksum ^= w;
if (segment_data->checksum != NULL) {
*segment_data->checksum ^= w;
}
#ifdef BOOTLOADER_BUILD
if (do_load) {
if (segment_data->do_load) {
dest[w_i] = w ^ ((w_i & 1) ? ram_obfs_value[0] : ram_obfs_value[1]);
}
#endif
@@ -801,13 +831,13 @@ static esp_err_t process_segment_data(int segment, intptr_t load_addr, uint32_t
// 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],
if (segment_data->sha_handle != NULL && i % SHA_CHUNK == 0) {
bootloader_sha256_data(segment_data->sha_handle, &src[w_i],
MIN(SHA_CHUNK, data_len - i));
}
}
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
if (do_load && esp_ptr_in_iram((uint32_t *)load_addr)) {
if (segment_data->do_load && esp_ptr_in_iram((uint32_t *)segment_data->load_addr)) {
/* If we have manipulated data over dcache that will be read over icache then we need
to writeback, else the data read might be invalid */
cache_ll_writeback_all(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA, CACHE_LL_ID_ALL);

View File

@@ -2,3 +2,7 @@ idf_component_register(SRCS "test_app_main.c" "test_verify_image.c"
INCLUDE_DIRS "."
REQUIRES unity bootloader_support esp_partition app_update
WHOLE_ARCHIVE)
target_link_libraries(${COMPONENT_LIB} INTERFACE
"-Wl,--wrap=bootloader_mmap_get_free_pages"
"-Wl,--wrap=bootloader_common_check_efuse_blk_validity")

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -21,6 +21,38 @@
#include "esp_partition.h"
#include "esp_ota_ops.h"
#include "esp_image_format.h"
#include "sdkconfig.h"
#define SPLIT_TEST_FREE_MMU_PAGES 2
#define SPLIT_TEST_MMU_PAGE_SIZE CONFIG_MMU_PAGE_SIZE
#define SPLIT_TEST_RODATA_SIZE (SPLIT_TEST_MMU_PAGE_SIZE + 64)
static uint32_t s_mmap_free_pages_override;
static const uint8_t s_split_test_rodata[SPLIT_TEST_RODATA_SIZE] = {
[0] = 0xA5,
[SPLIT_TEST_RODATA_SIZE - 1] = 0x5A,
};
uint32_t __wrap_bootloader_mmap_get_free_pages(void)
{
if (s_mmap_free_pages_override != 0) {
return s_mmap_free_pages_override;
}
extern uint32_t __real_bootloader_mmap_get_free_pages(void);
return __real_bootloader_mmap_get_free_pages();
}
#if !CONFIG_IDF_TARGET_ESP32
static uint32_t s_first_segment_app_desc_check_count;
esp_err_t __wrap_bootloader_common_check_efuse_blk_validity(uint32_t min_rev_full, uint32_t max_rev_full)
{
s_first_segment_app_desc_check_count++;
extern esp_err_t __real_bootloader_common_check_efuse_blk_validity(uint32_t min_rev_full, uint32_t max_rev_full);
return __real_bootloader_common_check_efuse_blk_validity(min_rev_full, max_rev_full);
}
#endif
TEST_CASE("Verify bootloader image in flash", "[bootloader_support]")
{
@@ -54,6 +86,40 @@ TEST_CASE("Verify unit test app image", "[bootloader_support]")
TEST_ASSERT_TRUE(data.image_len <= running->size);
}
TEST_CASE("Verify unit test app image with split first segment", "[bootloader_support]")
{
const volatile uint8_t *rodata = s_split_test_rodata;
TEST_ASSERT_EQUAL_HEX8(0xA5, rodata[0]);
TEST_ASSERT_EQUAL_HEX8(0x5A, rodata[SPLIT_TEST_RODATA_SIZE - 1]);
esp_image_metadata_t metadata = { 0 };
const esp_partition_t *running = esp_ota_get_running_partition();
TEST_ASSERT_NOT_NULL(running);
const esp_partition_pos_t running_pos = {
.offset = running->address,
.size = running->size,
};
TEST_ASSERT_EQUAL_HEX(ESP_OK, esp_image_verify(ESP_IMAGE_VERIFY, &running_pos, &metadata));
TEST_ASSERT_GREATER_THAN(SPLIT_TEST_MMU_PAGE_SIZE, metadata.segments[0].data_len);
TEST_ASSERT_NOT_EQUAL(0, metadata.segment_data[0] & (SPLIT_TEST_MMU_PAGE_SIZE - 1));
memset(&metadata, 0, sizeof(metadata));
#if !CONFIG_IDF_TARGET_ESP32
s_first_segment_app_desc_check_count = 0;
#endif
s_mmap_free_pages_override = SPLIT_TEST_FREE_MMU_PAGES;
esp_err_t ret = esp_image_verify(ESP_IMAGE_VERIFY, &running_pos, &metadata);
s_mmap_free_pages_override = 0;
TEST_ASSERT_EQUAL_HEX(ESP_OK, ret);
TEST_ASSERT_NOT_EQUAL(0, metadata.image_len);
#if !CONFIG_IDF_TARGET_ESP32
TEST_ASSERT_EQUAL_UINT32(1, s_first_segment_app_desc_check_count);
#endif
}
void check_label_search (int num_test, const char *list, const char *t_label, bool result)
{
// gen_esp32part.py trims up to 16 characters