diff --git a/core/recovery.c b/core/recovery.c index 0cffdab..08f778c 100644 --- a/core/recovery.c +++ b/core/recovery.c @@ -15,6 +15,7 @@ */ #include "eos_bootctl.h" +#include "eos_recovery.h" #include "eos_image.h" #include "eos_hal.h" #include "eos_crypto_boot.h" @@ -280,12 +281,9 @@ static int recovery_handle_write(eos_slot_t slot, uint32_t offset, uint16_t len) if (len > sizeof(buf)) return recovery_send_nack(); - /* offset/len come straight from the wire; without this check a - * recovery client can write past the slot boundary into the other - * slot, boot-control blocks, or the boot log. eos_recovery_write_in_range() - * is the single definition of that rule: it is what the unit tests - * exercise, and it also rejects an unmapped slot (base == 0) and a - * base + offset that wraps the address space. */ + /* offset/len come straight from the wire. The helper rejects a + * zero slot base, wrap of base+offset, and writes that run past + * the slot into boot-control, the other slot, or the boot log. */ if (eos_recovery_write_in_range(base, slot_size, offset, len) != EOS_OK) return recovery_send_nack(); diff --git a/include/eos_recovery.h b/include/eos_recovery.h new file mode 100644 index 0000000..33d7011 --- /dev/null +++ b/include/eos_recovery.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2026 EoS Project +// ISO/IEC 25000 | ISO/IEC/IEEE 15288:2023 + +/** + * @file eos_recovery.h + * @brief UART recovery mode entry and write-range checks + */ + +#ifndef EOS_RECOVERY_H +#define EOS_RECOVERY_H + +#include "eos_bootctl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Return EOS_OK if a recovery write of @p len bytes at @p offset + * stays inside the slot at @p base. + * + * Rejects a zero base or slot size, a zero length, a write that runs + * past the slot, and wrap of @c base + offset. + */ +int eos_recovery_write_in_range(uint32_t base, uint32_t slot_size, + uint32_t offset, uint16_t len); + +/** + * @brief Enter the UART recovery command loop. Does not return on success. + */ +int eos_recovery_enter(eos_bootctl_t *bctl); + +#ifdef __cplusplus +} +#endif +#endif /* EOS_RECOVERY_H */ diff --git a/tests/unit/test_recovery.c b/tests/unit/test_recovery.c index 00cda17..8082314 100644 --- a/tests/unit/test_recovery.c +++ b/tests/unit/test_recovery.c @@ -14,6 +14,7 @@ */ #include "eos_bootctl.h" +#include "eos_recovery.h" #include "eos_hal.h" #include "eos_crypto_boot.h" #include @@ -192,8 +193,6 @@ static void script_append(const uint8_t *bytes, size_t len) script_len += len; } -extern int eos_recovery_enter(eos_bootctl_t *bctl); - /* ---- Test harness ---- */ static int tests_run = 0; @@ -228,6 +227,32 @@ static void setup(void) eos_hal_init(&sim_ops); } +TEST(test_write_in_range_rejects_zero_base) +{ + ASSERT(eos_recovery_write_in_range(0, 0x1000, 0, 16) == EOS_ERR_INVALID); +} + +TEST(test_write_in_range_rejects_zero_len) +{ + ASSERT(eos_recovery_write_in_range(0x4000, 0x1000, 0, 0) == EOS_ERR_INVALID); +} + +TEST(test_write_in_range_rejects_past_slot) +{ + ASSERT(eos_recovery_write_in_range(0x4000, 0x1000, 0x1000, 16) == EOS_ERR_INVALID); +} + +TEST(test_write_in_range_rejects_base_offset_wrap) +{ + ASSERT(eos_recovery_write_in_range(0xFFFFFFF0u, 0x1000, 0x20, 16) == EOS_ERR_INVALID); +} + +TEST(test_write_in_range_accepts_in_slot) +{ + ASSERT(eos_recovery_write_in_range(0x4000, 0x1000, 0, 16) == EOS_OK); + ASSERT(eos_recovery_write_in_range(0x4000, 0x1000, 0x1000 - 16, 16) == EOS_OK); +} + /* Authenticate, then drive an out-of-bounds WRITE (offset+len past the end * of slot A) followed by a well-formed in-bounds WRITE. Before the fix, * recovery_handle_write() never validated `offset` against the slot size, @@ -290,6 +315,11 @@ TEST(test_write_rejects_offset_past_slot_end) int main(void) { printf("=== test_recovery ===\n"); + run_test_write_in_range_rejects_zero_base(); + run_test_write_in_range_rejects_zero_len(); + run_test_write_in_range_rejects_past_slot(); + run_test_write_in_range_rejects_base_offset_wrap(); + run_test_write_in_range_accepts_in_slot(); run_test_write_rejects_offset_past_slot_end(); printf("%d/%d tests passed\n", tests_passed, tests_run); return (tests_passed == tests_run) ? 0 : 1;