Skip to content
Open
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 @@ -111,6 +111,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 @@ -316,8 +316,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
9 changes: 9 additions & 0 deletions core/secure_boot.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ eos_secure_boot_result_t eos_secure_boot(const eos_secure_boot_config_t *cfg,
return EOS_SBOOT_ERR_BAD_HEADER;
}

/* The complete image must fit in the configured slot before any
* integrity/TLV processing can read beyond the slot boundary. */
if (cfg->slot_size == 0 ||
!eos_image_fits_slot(&hdr, cfg->slot_size)) {
attest_record(2, hdr.image_version, hdr.hash, NULL,
EOS_SBOOT_ERR_BAD_HEADER);
return EOS_SBOOT_ERR_BAD_HEADER;
}

/* ---- Step 2: Verify integrity (SHA-256 hash) ---- */
/* Bug Fix: Pass image_addr directly to verify_integrity since verify_integrity already adds hdr_size */
rc = eos_image_verify_integrity(&hdr, cfg->image_addr);
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
1 change: 1 addition & 0 deletions include/eos_secure_boot.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ typedef struct {
bool require_encryption; /* Enforce AES-GCM decryption */
bool lock_debug; /* Disable SWD/JTAG after boot */
bool enable_attestation; /* Log boot measurements */
uint32_t slot_size; /* Flash capacity available to this image */
} eos_secure_boot_config_t;

/* ---- Secure Boot Result ---- */
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 @@ -72,7 +72,7 @@ target_link_libraries(eboot_test_slot_size_bounds PRIVATE eboot_core)
add_test(NAME test_slot_size_bounds COMMAND eboot_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)

# --- test_device_table: UEFI-style device table ---
Expand Down
28 changes: 25 additions & 3 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 @@ -184,14 +186,34 @@ 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();

tests_run = 2;
tests_run = 3;

printf("\n%d/%d tests passed\n", tests_passed, tests_run);

Expand Down
19 changes: 19 additions & 0 deletions tests/unit/test_secure_boot.c
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ static eos_secure_boot_config_t base_cfg(void)
eos_secure_boot_config_t cfg;
memset(&cfg, 0, sizeof(cfg));
cfg.image_addr = IMAGE_ADDR;
cfg.slot_size = 0x8000u;
cfg.require_signature = false;
cfg.lock_debug = false;
return cfg;
Expand Down Expand Up @@ -198,13 +199,31 @@ TEST(test_decrypt_failure_is_attested)
ASSERT(log->entries[log->count - 1].verify_result == EOS_SBOOT_ERR_DECRYPT);
}

TEST(test_tlv_beyond_slot_is_rejected)
{
write_image(0);

eos_image_header_t hdr;
memcpy(&hdr, &sim_flash[IMAGE_ADDR], sizeof(hdr));

hdr.tlv_len = 1;
memcpy(&sim_flash[IMAGE_ADDR], &hdr, sizeof(hdr));

eos_secure_boot_config_t cfg = base_cfg();
cfg.slot_size = sizeof(eos_image_header_t) + PAYLOAD_LEN;

uint32_t entry = 0;
ASSERT(eos_secure_boot(&cfg, &entry) == EOS_SBOOT_ERR_BAD_HEADER);
}

int main(void)
{
printf("Secure boot policy tests\n");
run_test_plaintext_image_rejected_when_encryption_required();
run_test_encrypted_image_rejected_while_decrypt_unimplemented();
run_test_plaintext_image_boots_when_encryption_not_required();
run_test_decrypt_failure_is_attested();
run_test_tlv_beyond_slot_is_rejected();
/* Compare, and let the exit code carry it. `return 0` meant a suite that
* ran nothing at all still reported success -- the ASSERT macro exits on
* failure, so the only thing this return could ever have signalled is
Expand Down
4 changes: 4 additions & 0 deletions tests/unit/test_secure_boot_policy.c
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ TEST(test_secure_boot_refuses_when_the_debug_lock_cannot_be_taken)

eos_secure_boot_config_t cfg;
memset(&cfg, 0, sizeof(cfg));
cfg.slot_size = FLASH_SIZE;
cfg.image_addr = FLASH_BASE;
cfg.require_signature = false;
cfg.require_encryption = false;
Expand All @@ -201,6 +202,7 @@ TEST(test_the_same_image_boots_when_no_debug_lock_is_asked_for)

eos_secure_boot_config_t cfg;
memset(&cfg, 0, sizeof(cfg));
cfg.slot_size = FLASH_SIZE;
cfg.image_addr = FLASH_BASE;
cfg.require_signature = false;
cfg.require_encryption = false;
Expand All @@ -220,6 +222,7 @@ TEST(test_secure_boot_proceeds_when_the_debug_lock_succeeds)

eos_secure_boot_config_t cfg;
memset(&cfg, 0, sizeof(cfg));
cfg.slot_size = FLASH_SIZE;
cfg.image_addr = FLASH_BASE;
cfg.require_signature = false;
cfg.require_encryption = false;
Expand Down Expand Up @@ -257,6 +260,7 @@ TEST(test_the_ordinary_boot_path_is_unaffected_by_the_step_4_change)

eos_secure_boot_config_t cfg;
memset(&cfg, 0, sizeof(cfg));
cfg.slot_size = FLASH_SIZE;
cfg.image_addr = FLASH_BASE;
cfg.require_signature = false;
cfg.lock_debug = false;
Expand Down
Loading