diff --git a/CMakeLists.txt b/CMakeLists.txt index a6616fc..b9df7da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -170,6 +170,7 @@ add_library(eboot_core STATIC core/debug_lock.c core/fw_decrypt.c core/image_tlv.c + core/image_slot_bounds.c ) target_include_directories(eboot_core PUBLIC ${EBLDR_INCLUDE_DIR}) target_link_libraries(eboot_core PUBLIC eboot_hal) diff --git a/core/image_slot_bounds.c b/core/image_slot_bounds.c new file mode 100644 index 0000000..00c961b --- /dev/null +++ b/core/image_slot_bounds.c @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2026 EoS Project +// ISO/IEC 25000 | ISO/IEC/IEEE 15288:2023 + +#include "eos_image.h" + +bool eos_image_fits_slot(const eos_image_header_t *hdr, uint32_t slot_size) +{ + if (!hdr) + return false; + + if (hdr->hdr_size > slot_size) + return false; + + uint32_t remaining = slot_size - hdr->hdr_size; + + if (hdr->image_size > remaining) + return false; + + remaining -= hdr->image_size; + + if (hdr->tlv_len > remaining) + return false; + + return true; +} diff --git a/core/recovery.c b/core/recovery.c index fafdc95..1356b81 100644 --- a/core/recovery.c +++ b/core/recovery.c @@ -374,8 +374,7 @@ static int recovery_handle_verify(eos_slot_t slot) uint32_t slot_size = eos_hal_slot_size(slot); if (slot_size == 0 || - hdr.hdr_size > slot_size || - hdr.image_size > slot_size - hdr.hdr_size) + !eos_image_fits_slot(&hdr, slot_size)) return recovery_send_nack(); /* eos_image_verify_integrity() adds hdr_size internally — pass base addr only */ diff --git a/core/slot_manager.c b/core/slot_manager.c index 08d8368..b46da6f 100644 --- a/core/slot_manager.c +++ b/core/slot_manager.c @@ -52,8 +52,7 @@ static int verify_slot(eos_slot_t slot) * boot, did not. */ uint32_t slot_size = eos_hal_slot_size(slot); if (slot_size == 0 || - si->header.hdr_size > slot_size || - si->header.image_size > slot_size - si->header.hdr_size) { + !eos_image_fits_slot(&si->header, slot_size)) { si->state = EOS_SLOT_STATE_INVALID; return EOS_ERR_INVALID; } diff --git a/include/eos_image.h b/include/eos_image.h index 24cbb16..0752e3a 100644 --- a/include/eos_image.h +++ b/include/eos_image.h @@ -208,6 +208,17 @@ int eos_image_verify_signature(const eos_image_header_t *hdr); */ int eos_image_check_version(uint32_t candidate_version, uint32_t min_version); +/** + * @brief Check whether an image fits completely within its flash slot. + * + * Validates the image header, payload, and TLV area against the slot size. + * + * @param hdr Image header. + * @param slot_size Available slot size in bytes. + * @return true if the complete image fits within the slot, false otherwise. + */ +bool eos_image_fits_slot(const eos_image_header_t *hdr, uint32_t slot_size); + /** * @brief Compute CRC32 over a flash region, reporting read failures. * diff --git a/stage1/jump_app.c b/stage1/jump_app.c index 5ed653f..dbac56e 100644 --- a/stage1/jump_app.c +++ b/stage1/jump_app.c @@ -32,12 +32,11 @@ int eboot_jump_to_app(eos_bootctl_t *bctl, eos_slot_t slot) if (rc != EOS_OK) return rc; - /* Defense-in-depth: ensure the image fits within the actual slot - * before any integrity verification can stream payload bytes. */ + /* Ensure the complete image, including TLVs, fits within the slot + * before any integrity verification can read beyond the slot. */ uint32_t slot_size = eos_hal_slot_size(slot); if (slot_size == 0 || - hdr.hdr_size > slot_size || - hdr.image_size > slot_size - hdr.hdr_size) { + !eos_image_fits_slot(&hdr, slot_size)) { eos_boot_log_append(EOS_LOG_IMAGE_INVALID, slot, EOS_ERR_INVALID); return EOS_ERR_INVALID; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4150393..fed1bb1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -76,7 +76,7 @@ add_test(NAME test_slot_size_bounds COMMAND eboot_test_slot_size_bounds) list(APPEND EBLDR_UNIT_TESTS test_slot_size_bounds) add_executable(eboot_test_jump_app_bounds unit/test_jump_app_bounds.c) -target_link_libraries(eboot_test_jump_app_bounds PRIVATE eboot_core eboot_stage1) +target_link_libraries(eboot_test_jump_app_bounds PRIVATE eboot_stage1) add_test(NAME test_jump_app_bounds COMMAND eboot_test_jump_app_bounds) list(APPEND EBLDR_UNIT_TESTS test_jump_app_bounds) diff --git a/tests/unit/test_jump_app_bounds.c b/tests/unit/test_jump_app_bounds.c index 496e161..7b893c9 100644 --- a/tests/unit/test_jump_app_bounds.c +++ b/tests/unit/test_jump_app_bounds.c @@ -37,8 +37,10 @@ static int sim_flash_read(uint32_t addr, void *buf, size_t len) memcpy(buf, &sim_flash[addr], len); - if (addr >= SLOT_A_ADDR + sizeof(eos_image_header_t) && - addr < SLOT_B_ADDR) + uint32_t payload_start = SLOT_A_ADDR + sizeof(eos_image_header_t); + + if (addr >= payload_start && + addr < SLOT_A_ADDR + SLOT_A_SIZE) payload_bytes_read += len; return EOS_OK; @@ -185,12 +187,32 @@ TEST(test_in_bounds_image_reaches_integrity_check) ASSERT(payload_bytes_read == image_size); } +TEST(test_tlv_beyond_slot_rejected_before_payload_read) +{ + eos_image_header_t hdr; + eos_bootctl_t bctl; + + uint32_t image_size = + SLOT_A_SIZE - sizeof(eos_image_header_t); + + fill_header(&hdr, image_size); + hdr.tlv_len = 1; + write_header(SLOT_A_ADDR, &hdr); + memset(&bctl, 0, sizeof(bctl)); + + int rc = eboot_jump_to_app(&bctl, EOS_SLOT_A); + + ASSERT(rc == EOS_ERR_INVALID); + ASSERT(payload_bytes_read == 0); +} + int main(void) { printf("=== eBootloader: Jump-App Slot-Size Bounds Tests ===\n\n"); run_test_oversized_image_rejected_before_reading_payload(); run_test_in_bounds_image_reaches_integrity_check(); + run_test_tlv_beyond_slot_rejected_before_payload_read(); printf("\n%d/%d tests passed\n", tests_passed, tests_run);