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-05-14 14:43:29 +08:00
committed by BOT
parent 46305c923e
commit ee127fd4de
3 changed files with 127 additions and 27 deletions
@@ -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 * 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 */ /* 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); 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 */ /* 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 */ /* Verify the main image header */
static esp_err_t verify_image_header(uint32_t src_addr, const esp_image_header_t *image, bool silent); 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(); uint32_t free_page_count = bootloader_mmap_get_free_pages();
ESP_LOGD(TAG, "free data page_count 0x%08"PRIx32, free_page_count); 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; uint32_t data_len_remain = data_len;
while (data_len_remain > 0) { while (data_len_remain > 0) {
#if (SECURE_BOOT_CHECK_SIGNATURE == 1) && defined(BOOTLOADER_BUILD) #if (SECURE_BOOT_CHECK_SIGNATURE == 1) && defined(BOOTLOADER_BUILD)
/* Double check the address verification done above */ /* 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 #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. */ /* 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; uint32_t max_pages = (free_page_count > offset_page) ? (free_page_count - offset_page) : 0;
if (max_pages == 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)) { if (__builtin_mul_overflow(max_pages, SPI_FLASH_MMU_PAGE_SIZE, &max_image_len)) {
max_image_len = UINT32_MAX; max_image_len = UINT32_MAX;
} }
data_len = MIN(data_len_remain, max_image_len); segment_data.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)); CHECK_ERR(process_segment_data(&segment_data));
data_addr += data_len; segment_data.data_addr += segment_data.data_len;
data_len_remain -= 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; 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 #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 // If we are not loading, and the checksum is empty, skip processing this
// segment for data // segment for data
if (!do_load && checksum == NULL) { if (!segment_data->do_load && segment_data->checksum == NULL) {
ESP_LOGD(TAG, "skipping checksum for segment"); ESP_LOGD(TAG, "skipping checksum for segment");
return ESP_OK; 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) { if (!data) {
ESP_LOGE(TAG, "bootloader_mmap(0x%"PRIx32", 0x%"PRIx32") failed", 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; return ESP_FAIL;
} }
if (checksum == NULL && sha_handle == NULL) { if (segment_data->checksum == NULL && segment_data->sha_handle == NULL) {
memcpy((void *)load_addr, data, data_len); memcpy((void *)segment_data->load_addr, data, segment_data->data_len);
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE #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 /* 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 */ 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); 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; ram_obfs_value[1] ^= 0x66;
#endif #endif
} }
uint32_t *dest = (uint32_t *)load_addr; uint32_t *dest = (uint32_t *)segment_data->load_addr;
#endif // BOOTLOADER_BUILD #endif // BOOTLOADER_BUILD
const uint32_t *src = data; const uint32_t *src = data;
uint32_t data_len = segment_data->data_len;
// Case I: Bootloader verifying application // Case I: Bootloader verifying application
// Case II: Bootloader verifying bootloader // Case II: Bootloader verifying bootloader
// The esp_app_desc_t structure is located in DROM and is always in segment #0. // 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. // 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. */ /* ESP32 doesn't have more memory and more efuse bits for block major version. */
#if !CONFIG_IDF_TARGET_ESP32 #if !CONFIG_IDF_TARGET_ESP32
const esp_app_desc_t *app_desc = (const esp_app_desc_t *)src; 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) { if (ret != ESP_OK) {
bootloader_munmap(data); bootloader_munmap(data);
return ret; return ret;
} }
#endif // !CONFIG_IDF_TARGET_ESP32 #endif // !CONFIG_IDF_TARGET_ESP32
#if CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK #if CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK
ESP_LOGD(TAG, "additional anti-rollback check 0x%"PRIx32, data_addr); ESP_LOGD(TAG, "additional anti-rollback check 0x%"PRIx32, segment_data->data_addr);
size_t len = process_esp_app_desc_data(src, sha_handle, checksum, metadata); size_t len = process_esp_app_desc_data(src, segment_data->sha_handle,
segment_data->checksum, segment_data->metadata);
data_len -= len; data_len -= len;
src += len / 4; src += len / 4;
// In BOOTLOADER_BUILD, for DROM (segment #0) we do not load it into dest (only map it), do_load = false. // 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) { for (size_t i = 0; i < data_len; i += 4) {
int w_i = i / 4; // Word index int w_i = i / 4; // Word index
uint32_t w = src[w_i]; uint32_t w = src[w_i];
if (checksum != NULL) { if (segment_data->checksum != NULL) {
*checksum ^= w; *segment_data->checksum ^= w;
} }
#ifdef BOOTLOADER_BUILD #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]); dest[w_i] = w ^ ((w_i & 1) ? ram_obfs_value[0] : ram_obfs_value[1]);
} }
#endif #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 // counter-intuitive, but it's ~3ms better than using the
// SHA256 block size. // SHA256 block size.
const size_t SHA_CHUNK = 1024; const size_t SHA_CHUNK = 1024;
if (sha_handle != NULL && i % SHA_CHUNK == 0) { if (segment_data->sha_handle != NULL && i % SHA_CHUNK == 0) {
bootloader_sha256_data(sha_handle, &src[w_i], bootloader_sha256_data(segment_data->sha_handle, &src[w_i],
MIN(SHA_CHUNK, data_len - i)); MIN(SHA_CHUNK, data_len - i));
} }
} }
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE #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 /* 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 */ 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); cache_ll_writeback_all(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA, CACHE_LL_ID_ALL);
@@ -2,3 +2,7 @@ idf_component_register(SRCS "test_app_main.c" "test_verify_image.c"
INCLUDE_DIRS "." INCLUDE_DIRS "."
REQUIRES unity bootloader_support esp_partition app_update REQUIRES unity bootloader_support esp_partition app_update
WHOLE_ARCHIVE) WHOLE_ARCHIVE)
target_link_libraries(${COMPONENT_LIB} INTERFACE
"-Wl,--wrap=bootloader_mmap_get_free_pages"
"-Wl,--wrap=bootloader_common_check_efuse_blk_validity")
@@ -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 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -21,6 +21,38 @@
#include "esp_partition.h" #include "esp_partition.h"
#include "esp_ota_ops.h" #include "esp_ota_ops.h"
#include "esp_image_format.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]") 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_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) void check_label_search (int num_test, const char *list, const char *t_label, bool result)
{ {
// gen_esp32part.py trims up to 16 characters // gen_esp32part.py trims up to 16 characters