mirror of
https://github.com/espressif/esp-idf.git
synced 2026-05-28 16:46:31 +03:00
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:
committed by
Konstantin Kondrashov
parent
67d1536ec2
commit
86d1448467
@@ -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);
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user