Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
26 changes: 26 additions & 0 deletions core/image_slot_bounds.c
Original file line number Diff line number Diff line change
@@ -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;
}
3 changes: 1 addition & 2 deletions core/recovery.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
3 changes: 1 addition & 2 deletions core/slot_manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
11 changes: 11 additions & 0 deletions include/eos_image.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
7 changes: 3 additions & 4 deletions stage1/jump_app.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
26 changes: 24 additions & 2 deletions tests/unit/test_jump_app_bounds.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Loading