From e5c12b41995e7066417ee0ba314c828193d7d200 Mon Sep 17 00:00:00 2001 From: Prakhar Maheshwari Date: Sun, 30 Aug 2026 18:56:52 +0530 Subject: [PATCH] fix(build): repair merge artifacts that left the tree uncompilable The tree did not build. `gcc -c core/image_verify.c` failed on the first core source file, and once past it two more sources and eleven of the seventeen unit tests could not compile or link. Every defect here is a leftover from a merge, not a design decision. Sources - include/eos_image.h declared `int eos_crc32(uint32_t, size_t, uint32_t *)` while core/image_verify.c defines `uint32_t eos_crc32(uint32_t, size_t)`. Conflicting types; the build stopped there. The declaration now matches the definition and the doc comment that sits above it. - core/ed25519_verify.c: eos_ed25519_verify() never verified anything. Two copies of the challenge-hash step had been merged into the function, the second calling identifiers that do not exist (sha512_ctx_t, sc_reduce), and RFC 8032 step 4 -- the [S]B == R + [k]A check -- was missing outright, leaving the function returning an undeclared `diff`. The duplicate is removed and the group equation restored using the primitives already in the file. - core/recovery.c: recovery_handle_write() declared slot_size twice. The bounds check now goes through eos_recovery_write_in_range(), which the unit tests already exercise, so the rule for wire-supplied offset/len has a single definition and an unmapped slot (base == 0) is rejected too. - SHA-512 had two incompatible declarations: include/eos_sha512.h (sha512_*, sha512_ctx_t) against include/eos_crypto_boot.h (eos_sha512_*, eos_sha512_ctx_t, plus a one-shot eos_sha512()). core/sha512.c implemented the first; ed25519_verify.c and the tests called the second, which nothing defined. core/sha512.c now implements the eos_-prefixed API including the missing one-shot, eos_sha512_ctx_t keeps the 128-bit length counter FIPS 180-4 requires, and the duplicate header is deleted. Build files - core/sha512.c and core/rollback.c were never compiled, so eboot_core could not resolve eos_sha512_* or eos_rollback_*. core/boot_log.c was listed twice. - The EBLDR_BOARD dispatch chain was duplicated from cortex_m3 onward with a stray message(FATAL_ERROR ...) inside the kalimba branch, so `cmake -DEBLDR_BOARD=kalimba` aborted configuration for a supported board and 110 later branches were unreachable. This is exactly the regression tests/unit/test_cmake_board_dispatch.py was written to catch; it had come back and those three tests were failing on master. - tests/unit/test_fw_transport.c existed but was never built or run. Tests - tests/unit/test_slot_manager.c was two different test files spliced together mid-function: stub bodies cut in half, and a main() calling twenty functions that are not in the file. Rebuilt as one suite that drives the real core/slot_manager.c through scriptable per-slot mocks of the three verification stages, following the harness conventions in test_slot_size_bounds.c. - tests/unit/test_recovery.c defined local boot-log stand-ins that conflicted with include/eos_boot_log.h and duplicated symbols now linked from core/boot_log.c. Verified: cmake configure, `cmake --build` (0 errors) and `ctest` 17/17 pass; `pytest tests` 27/27 pass, including the three board-dispatch tests that were failing before this change. test_ed25519 exercises the RFC 8032 vectors, tampered messages, every single-bit signature flip, wrong keys and malleated signatures. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QjaFDcvWmF1ApZQedLXJz5 --- CHANGELOG.md | 11 ++ CMakeLists.txt | 114 +---------- core/ed25519_verify.c | 31 +-- core/recovery.c | 8 +- core/sha512.c | 26 ++- include/eos_crypto_boot.h | 5 +- include/eos_image.h | 2 +- include/eos_sha512.h | 23 --- tests/CMakeLists.txt | 5 + tests/unit/test_recovery.c | 5 - tests/unit/test_slot_manager.c | 351 +++++++++++++++++++-------------- 11 files changed, 263 insertions(+), 318 deletions(-) delete mode 100644 include/eos_sha512.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 631a502..c0eaa82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,17 @@ - **`image_verify.c`:** The CRC32 integrity path now fails closed on a flash read error. `eos_crc32()` returned `0` when `eos_hal_flash_read()` failed, which is indistinguishable from a region that genuinely hashes to `0`, so an image whose payload could not be read passed `eos_image_verify_integrity()` when the stored CRC was `0`. The stored CRC lives in the unauthenticated header, so setting it to `0` is trivial. The SHA-256 path already propagated the read error; the two now behave the same. - **`image_verify.c`:** `eos_image_verify_integrity` rejects a zero `image_size`, and an `addr + hdr_size` that wraps `uint32_t`, instead of computing a payload address that is not the payload. +### Fixed +- **The tree did not compile.** `include/eos_image.h` declared `eos_crc32()` as `int eos_crc32(uint32_t, size_t, uint32_t *)` while `core/image_verify.c` defined it as `uint32_t eos_crc32(uint32_t, size_t)` -- a conflicting-types error that stopped the build at the first core source file. The declaration now matches the definition and the documented behaviour. +- **`ed25519_verify.c`:** `eos_ed25519_verify()` never performed the verification. Two merged copies of the challenge-hash step had been left in the function, the second referring to identifiers that do not exist (`sha512_ctx_t`, `sc_reduce`), and RFC 8032 step 4 -- the `[S]B == R + [k]A` check -- was absent entirely, leaving the function returning an undeclared `diff`. The duplicate is removed and the group-equation check restored; the function now passes the RFC 8032 test vectors and rejects tampered messages, every single-bit signature flip, wrong keys and malleated signatures. +- **`recovery.c`:** `recovery_handle_write()` declared `slot_size` twice, which does not compile. The bounds check now calls `eos_recovery_write_in_range()` -- the helper the unit tests already exercise -- so the wire-input rule has one definition, and an unmapped slot (`base == 0`) is rejected too. +- **SHA-512 had two incompatible declarations.** `include/eos_sha512.h` declared `sha512_init/update/final` over a `sha512_ctx_t`, while `include/eos_crypto_boot.h` declared `eos_sha512_init/update/final` plus a one-shot `eos_sha512()` over an `eos_sha512_ctx_t`. `core/sha512.c` implemented the first set; `core/ed25519_verify.c` and the unit tests called the second, which nothing defined. `core/sha512.c` now implements the `eos_`-prefixed API (including the missing one-shot), `eos_sha512_ctx_t` carries the 128-bit length counter FIPS 180-4 requires, and the duplicate `include/eos_sha512.h` is removed. +- **`CMakeLists.txt`:** `core/sha512.c` and `core/rollback.c` were never compiled, so `eboot_core` could not resolve `eos_sha512_*` or `eos_rollback_*` and eleven unit-test executables failed to link. `core/boot_log.c` was listed twice. Both fixed. +- **`CMakeLists.txt`:** the `EBLDR_BOARD` dispatch chain was duplicated from `cortex_m3` onwards, and a stray `message(FATAL_ERROR ...)` sat inside the `kalimba` branch, so `cmake -DEBLDR_BOARD=kalimba` aborted configuration for a supported board and 110 later branches were unreachable. The duplicate is removed. (This is the regression `tests/unit/test_cmake_board_dispatch.py` was written to catch; it had returned.) +- **`tests/unit/test_slot_manager.c`:** the file was two different test files spliced together mid-function -- stub definitions cut in half, `main()` calling twenty functions that do not exist. Rebuilt as one suite that exercises the real `core/slot_manager.c` through scriptable per-slot verification mocks. +- **`tests/unit/test_recovery.c`:** local stand-ins for the boot-log API conflicted with `include/eos_boot_log.h` and duplicated symbols now linked from `core/boot_log.c`. Removed. +- **`tests/CMakeLists.txt`:** `unit/test_fw_transport.c` existed but was never built or run. It is now registered. + ### Added - **`eos_crc32_checked()`** — CRC32 over a flash region that reports read failures through its return value. `eos_crc32()` is retained for API compatibility and documented as unsuitable for verification decisions. diff --git a/CMakeLists.txt b/CMakeLists.txt index a6d5f5d..ff92704 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,7 +84,6 @@ add_library(eboot_core STATIC core/image_verify.c core/slot_manager.c core/boot_policy.c - core/boot_log.c core/recovery.c core/fw_services.c core/fw_update.c @@ -111,6 +110,8 @@ add_library(eboot_core STATIC core/debug_lock.c core/fw_decrypt.c core/image_tlv.c + core/sha512.c + core/rollback.c ) target_include_directories(eboot_core PUBLIC ${EBLDR_INCLUDE_DIR}) target_link_libraries(eboot_core PUBLIC eboot_hal) @@ -207,118 +208,7 @@ if(NOT EBLDR_BOARD STREQUAL "none") eboot_add_board(cortex_m0 boards/cortex_m0/board_cortex_m0.c boards/cortex_m0) elseif(EBLDR_BOARD STREQUAL "cortex_m0plus") eboot_add_board(cortex_m0plus boards/cortex_m0plus/board_cortex_m0plus.c boards/cortex_m0plus) - elseif(EBLDR_BOARD STREQUAL "cortex_m3") - eboot_add_board(cortex_m3 boards/cortex_m3/board_cortex_m3.c boards/cortex_m3) - elseif(EBLDR_BOARD STREQUAL "cortex_m23") - eboot_add_board(cortex_m23 boards/cortex_m23/board_cortex_m23.c boards/cortex_m23) - elseif(EBLDR_BOARD STREQUAL "cortex_m33") - eboot_add_board(cortex_m33 boards/cortex_m33/board_cortex_m33.c boards/cortex_m33) - elseif(EBLDR_BOARD STREQUAL "cortex_m55") - eboot_add_board(cortex_m55 boards/cortex_m55/board_cortex_m55.c boards/cortex_m55) - elseif(EBLDR_BOARD STREQUAL "cortex_m85") - eboot_add_board(cortex_m85 boards/cortex_m85/board_cortex_m85.c boards/cortex_m85) - elseif(EBLDR_BOARD STREQUAL "cortex_r4") - eboot_add_board(cortex_r4 boards/cortex_r4/board_cortex_r4.c boards/cortex_r4) - elseif(EBLDR_BOARD STREQUAL "cortex_r52") - eboot_add_board(cortex_r52 boards/cortex_r52/board_cortex_r52.c boards/cortex_r52) - elseif(EBLDR_BOARD STREQUAL "cortex_a5") - eboot_add_board(cortex_a5 boards/cortex_a5/board_cortex_a5.c boards/cortex_a5) - elseif(EBLDR_BOARD STREQUAL "cortex_a9") - eboot_add_board(cortex_a9 boards/cortex_a9/board_cortex_a9.c boards/cortex_a9) - elseif(EBLDR_BOARD STREQUAL "cortex_a15") - eboot_add_board(cortex_a15 boards/cortex_a15/board_cortex_a15.c boards/cortex_a15) - elseif(EBLDR_BOARD STREQUAL "cortex_a35") - eboot_add_board(cortex_a35 boards/cortex_a35/board_cortex_a35.c boards/cortex_a35) - elseif(EBLDR_BOARD STREQUAL "cortex_a55") - eboot_add_board(cortex_a55 boards/cortex_a55/board_cortex_a55.c boards/cortex_a55) - elseif(EBLDR_BOARD STREQUAL "cortex_a76") - eboot_add_board(cortex_a76 boards/cortex_a76/board_cortex_a76.c boards/cortex_a76) - elseif(EBLDR_BOARD STREQUAL "arm7tdmi") - eboot_add_board(arm7tdmi boards/arm7tdmi/board_arm7tdmi.c boards/arm7tdmi) - elseif(EBLDR_BOARD STREQUAL "arm9") - eboot_add_board(arm9 boards/arm9/board_arm9.c boards/arm9) - elseif(EBLDR_BOARD STREQUAL "arm11") - eboot_add_board(arm11 boards/arm11/board_arm11.c boards/arm11) - elseif(EBLDR_BOARD STREQUAL "avr") - eboot_add_board(avr boards/avr/board_avr.c boards/avr) - elseif(EBLDR_BOARD STREQUAL "avr32") - eboot_add_board(avr32 boards/avr32/board_avr32.c boards/avr32) - elseif(EBLDR_BOARD STREQUAL "pic16") - eboot_add_board(pic16 boards/pic16/board_pic16.c boards/pic16) - elseif(EBLDR_BOARD STREQUAL "pic18") - eboot_add_board(pic18 boards/pic18/board_pic18.c boards/pic18) - elseif(EBLDR_BOARD STREQUAL "pic24") - eboot_add_board(pic24 boards/pic24/board_pic24.c boards/pic24) - elseif(EBLDR_BOARD STREQUAL "dspic") - eboot_add_board(dspic boards/dspic/board_dspic.c boards/dspic) - elseif(EBLDR_BOARD STREQUAL "pic32") - eboot_add_board(pic32 boards/pic32/board_pic32.c boards/pic32) - elseif(EBLDR_BOARD STREQUAL "msp430") - eboot_add_board(msp430 boards/msp430/board_msp430.c boards/msp430) - elseif(EBLDR_BOARD STREQUAL "c28x") - eboot_add_board(c28x boards/c28x/board_c28x.c boards/c28x) - elseif(EBLDR_BOARD STREQUAL "c6000") - eboot_add_board(c6000 boards/c6000/board_c6000.c boards/c6000) - elseif(EBLDR_BOARD STREQUAL "pru") - eboot_add_board(pru boards/pru/board_pru.c boards/pru) - elseif(EBLDR_BOARD STREQUAL "rl78") - eboot_add_board(rl78 boards/rl78/board_rl78.c boards/rl78) - elseif(EBLDR_BOARD STREQUAL "rx") - eboot_add_board(rx boards/rx/board_rx.c boards/rx) - elseif(EBLDR_BOARD STREQUAL "tricore") - eboot_add_board(tricore boards/tricore/board_tricore.c boards/tricore) - elseif(EBLDR_BOARD STREQUAL "c166") - eboot_add_board(c166 boards/c166/board_c166.c boards/c166) - elseif(EBLDR_BOARD STREQUAL "microblaze") - eboot_add_board(microblaze boards/microblaze/board_microblaze.c boards/microblaze) - elseif(EBLDR_BOARD STREQUAL "nios2") - eboot_add_board(nios2 boards/nios2/board_nios2.c boards/nios2) - elseif(EBLDR_BOARD STREQUAL "openrisc") - eboot_add_board(openrisc boards/openrisc/board_openrisc.c boards/openrisc) - elseif(EBLDR_BOARD STREQUAL "lm32") - eboot_add_board(lm32 boards/lm32/board_lm32.c boards/lm32) - elseif(EBLDR_BOARD STREQUAL "blackfin") - eboot_add_board(blackfin boards/blackfin/board_blackfin.c boards/blackfin) - elseif(EBLDR_BOARD STREQUAL "sharc") - eboot_add_board(sharc boards/sharc/board_sharc.c boards/sharc) - elseif(EBLDR_BOARD STREQUAL "hexagon") - eboot_add_board(hexagon boards/hexagon/board_hexagon.c boards/hexagon) - elseif(EBLDR_BOARD STREQUAL "ceva") - eboot_add_board(ceva boards/ceva/board_ceva.c boards/ceva) - elseif(EBLDR_BOARD STREQUAL "xtensa_hifi") - eboot_add_board(xtensa_hifi boards/xtensa_hifi/board_xtensa_hifi.c boards/xtensa_hifi) - elseif(EBLDR_BOARD STREQUAL "arc") - eboot_add_board(arc boards/arc/board_arc.c boards/arc) - elseif(EBLDR_BOARD STREQUAL "8051") - eboot_add_board(8051 boards/8051/board_8051.c boards/8051) - elseif(EBLDR_BOARD STREQUAL "riscv32") - eboot_add_board(riscv32 boards/riscv32/board_riscv32.c boards/riscv32) - elseif(EBLDR_BOARD STREQUAL "esp32s3") - eboot_add_board(esp32s3 boards/esp32s3/board_esp32s3.c boards/esp32s3) - elseif(EBLDR_BOARD STREQUAL "esp32c3") - eboot_add_board(esp32c3 boards/esp32c3/board_esp32c3.c boards/esp32c3) - elseif(EBLDR_BOARD STREQUAL "mips64") - eboot_add_board(mips64 boards/mips64/board_mips64.c boards/mips64) - elseif(EBLDR_BOARD STREQUAL "sparc64") - eboot_add_board(sparc64 boards/sparc64/board_sparc64.c boards/sparc64) - elseif(EBLDR_BOARD STREQUAL "ppc64") - eboot_add_board(ppc64 boards/ppc64/board_ppc64.c boards/ppc64) - elseif(EBLDR_BOARD STREQUAL "loongarch") - eboot_add_board(loongarch boards/loongarch/board_loongarch.c boards/loongarch) - elseif(EBLDR_BOARD STREQUAL "parisc") - eboot_add_board(parisc boards/parisc/board_parisc.c boards/parisc) - elseif(EBLDR_BOARD STREQUAL "ia64") - eboot_add_board(ia64 boards/ia64/board_ia64.c boards/ia64) - elseif(EBLDR_BOARD STREQUAL "alpha") - eboot_add_board(alpha boards/alpha/board_alpha.c boards/alpha) - elseif(EBLDR_BOARD STREQUAL "s390") - eboot_add_board(s390 boards/s390/board_s390.c boards/s390) - elseif(EBLDR_BOARD STREQUAL "cris") - eboot_add_board(cris boards/cris/board_cris.c boards/cris) - elseif(EBLDR_BOARD STREQUAL "kalimba") - eboot_add_board(kalimba boards/kalimba/board_kalimba.c boards/kalimba) # --- New ARM Cortex-M boards --- - message(FATAL_ERROR "Unknown board: ${EBLDR_BOARD}. See boards/ directory for available ports.") elseif(EBLDR_BOARD STREQUAL "cortex_m3") eboot_add_board(cortex_m3 boards/cortex_m3/board_cortex_m3.c boards/cortex_m3) elseif(EBLDR_BOARD STREQUAL "cortex_m23") diff --git a/core/ed25519_verify.c b/core/ed25519_verify.c index 90ce54f..3838e53 100644 --- a/core/ed25519_verify.c +++ b/core/ed25519_verify.c @@ -29,7 +29,6 @@ #include "eos_crypto_boot.h" #include "eos_types.h" #include -#include "eos_sha512.h" /* ================================================================ * Field arithmetic mod p = 2^255 - 19 @@ -428,19 +427,23 @@ int eos_ed25519_verify(const uint8_t signature[64], eos_sha512_final(&ctx, k); reduce_hash(k); - /* Step 3: Compute k = SHA-256(R || A || M) reduced mod L */ - /* Step 3: Compute k = SHA-512(R || A || M) reduced mod L */ -uint8_t k_hash[64]; -sha512_ctx_t ctx; - -sha512_init(&ctx); -sha512_update(&ctx, signature, 32); /* R */ -sha512_update(&ctx, public_key, 32); /* A */ -sha512_update(&ctx, message, msg_len); /* M */ -sha512_final(&ctx, k_hash); - -uint8_t k[32]; -sc_reduce(k, k_hash); + /* Accept iff [S]B == R + [k]A, checked as [S]B - [k]A == R. + * + * A already holds -A (unpackneg() negates on decode), so [k]A' is the + * subtraction. scalarmult() consumes its point argument in place, which is + * why q is reloaded with the base point only after [k]A' has been formed. */ + gf p[4], q[4]; + scalarmult(p, A, k); /* p = [k](-A) */ + scalarbase(q, &signature[32]); /* q = [S]B */ + point_add(p, (const gf *)q); /* p = [S]B - [k]A */ + + uint8_t check[32]; + point_pack(check, p); + + /* Compare against R without an early exit, so a rejected signature costs + * the same time whatever byte it first differs at. */ + uint8_t diff = 0; + for (int i = 0; i < 32; i++) diff |= (uint8_t)(check[i] ^ signature[i]); return diff == 0 ? EOS_OK : EOS_ERR_SIGNATURE; } diff --git a/core/recovery.c b/core/recovery.c index 18a88ba..0344105 100644 --- a/core/recovery.c +++ b/core/recovery.c @@ -283,9 +283,11 @@ static int recovery_handle_write(eos_slot_t slot, uint32_t offset, uint16_t len) /* 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. */ - uint32_t slot_size = eos_hal_slot_size(slot); - if (slot_size == 0 || (uint64_t)offset + len > (uint64_t)slot_size) + * 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. */ + if (eos_recovery_write_in_range(base, slot_size, offset, len) != EOS_OK) return recovery_send_nack(); recovery_send_ack(); diff --git a/core/sha512.c b/core/sha512.c index 999baf2..8fb0b60 100644 --- a/core/sha512.c +++ b/core/sha512.c @@ -1,4 +1,4 @@ -#include "eos_sha512.h" +#include "eos_crypto_boot.h" #include #define ROTR64(x, n) (((x) >> (n)) | ((x) << (64 - (n)))) @@ -119,7 +119,7 @@ static void store_be64(uint8_t *p, uint64_t x) p[7] = (uint8_t)x; } -static void sha512_transform(sha512_ctx_t *ctx, +static void sha512_transform(eos_sha512_ctx_t *ctx, const uint8_t block[128]) { uint64_t w[80]; @@ -164,7 +164,7 @@ static void sha512_transform(sha512_ctx_t *ctx, ctx->state[7] += h; } -void sha512_init(sha512_ctx_t *ctx) +void eos_sha512_init(eos_sha512_ctx_t *ctx) { ctx->state[0] = 0x6a09e667f3bcc908ULL; ctx->state[1] = 0xbb67ae8584caa73bULL; @@ -180,9 +180,9 @@ void sha512_init(sha512_ctx_t *ctx) ctx->buffer_len = 0; } -void sha512_update(sha512_ctx_t *ctx, - const uint8_t *data, - size_t len) +void eos_sha512_update(eos_sha512_ctx_t *ctx, + const uint8_t *data, + size_t len) { while (len > 0) { size_t copy = 128 - ctx->buffer_len; @@ -213,8 +213,8 @@ void sha512_update(sha512_ctx_t *ctx, } } -void sha512_final(sha512_ctx_t *ctx, - uint8_t digest[64]) +void eos_sha512_final(eos_sha512_ctx_t *ctx, + uint8_t digest[EOS_SHA512_DIGEST_SIZE]) { size_t i = ctx->buffer_len; @@ -241,3 +241,13 @@ void sha512_final(sha512_ctx_t *ctx, memset(ctx, 0, sizeof(*ctx)); } + +void eos_sha512(const uint8_t *data, size_t len, + uint8_t digest[EOS_SHA512_DIGEST_SIZE]) +{ + eos_sha512_ctx_t ctx; + + eos_sha512_init(&ctx); + eos_sha512_update(&ctx, data, len); + eos_sha512_final(&ctx, digest); +} diff --git a/include/eos_crypto_boot.h b/include/eos_crypto_boot.h index 71ea9a1..6a82591 100644 --- a/include/eos_crypto_boot.h +++ b/include/eos_crypto_boot.h @@ -34,8 +34,11 @@ void eos_sha256_final(eos_sha256_ctx_t *ctx, uint8_t digest[EOS_SHA256_DIGEST_SI typedef struct { uint64_t state[8]; - uint64_t count; + /* FIPS 180-4 gives SHA-512 a 128-bit length field, so the bit count is + * carried in two words rather than one. */ + uint64_t bitlen[2]; uint8_t buffer[EOS_SHA512_BLOCK_SIZE]; + size_t buffer_len; } eos_sha512_ctx_t; /** diff --git a/include/eos_image.h b/include/eos_image.h index 049e6c8..a2a9d18 100644 --- a/include/eos_image.h +++ b/include/eos_image.h @@ -146,7 +146,7 @@ int eos_crc32_checked(uint32_t addr, size_t len, uint32_t *out_crc); * @param len Length in bytes. * @return CRC32 value, or 0 if the region could not be read. */ -int eos_crc32(uint32_t addr, size_t len, uint32_t *out); +uint32_t eos_crc32(uint32_t addr, size_t len); #ifdef __cplusplus } diff --git a/include/eos_sha512.h b/include/eos_sha512.h deleted file mode 100644 index e089561..0000000 --- a/include/eos_sha512.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef EOS_SHA512_H -#define EOS_SHA512_H - -#include -#include - -typedef struct { - uint64_t state[8]; - uint64_t bitlen[2]; - uint8_t buffer[128]; - size_t buffer_len; -} sha512_ctx_t; - -void sha512_init(sha512_ctx_t *ctx); - -void sha512_update(sha512_ctx_t *ctx, - const uint8_t *data, - size_t len); - -void sha512_final(sha512_ctx_t *ctx, - uint8_t digest[64]); - -#endif \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c9e95ff..3f4b679 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -81,6 +81,11 @@ add_executable(test_storage unit/test_storage.c) target_link_libraries(test_storage PRIVATE eboot_core) add_test(NAME test_storage COMMAND test_storage) +# --- test_fw_transport: XMODEM/YMODEM firmware transport --- +add_executable(test_fw_transport unit/test_fw_transport.c) +target_link_libraries(test_fw_transport PRIVATE eboot_core) +add_test(NAME test_fw_transport COMMAND test_fw_transport) + # --- Valgrind test targets --- find_program(VALGRIND valgrind) if(VALGRIND) diff --git a/tests/unit/test_recovery.c b/tests/unit/test_recovery.c index a87e80d..00cda17 100644 --- a/tests/unit/test_recovery.c +++ b/tests/unit/test_recovery.c @@ -23,11 +23,6 @@ #include #include "eos_boot_log.h" -/* Mock implementations for unresolved symbols */ -void eos_boot_log_append(uint32_t event, uint32_t slot, uint32_t detail) { (void)event; (void)slot; (void)detail; } -int eos_boot_log_read(uint32_t index, eos_boot_log_entry_t *out) { (void)index; (void)out; return EOS_OK; } -uint32_t eos_boot_log_get_head(void) { return 0; } - /* ---- Simulated Flash ---- */ #define SIM_FLASH_SIZE (128 * 1024) diff --git a/tests/unit/test_slot_manager.c b/tests/unit/test_slot_manager.c index 7c4254b..dbbd6ad 100644 --- a/tests/unit/test_slot_manager.c +++ b/tests/unit/test_slot_manager.c @@ -5,143 +5,205 @@ /** * @file test_slot_manager.c * @brief Unit tests for the production firmware slot manager + * + * These tests exercise the real core/slot_manager.c. The three image + * verification steps it calls -- eos_image_parse_header(), + * eos_image_verify_integrity() and eos_image_verify_signature() -- are + * replaced with per-slot scriptable mocks so each stage can be failed + * independently without having to build and sign real images. Flash and + * slot geometry come from the standard simulated eos_board_ops_t, so + * eos_hal_slot_addr()/eos_hal_slot_size() behave as they do on a board. */ -#include "eos_hal.h" #include "eos_slot_manager.h" +#include "eos_image.h" +#include "eos_hal.h" #include #include #include -/* ---- Simulated Flash Backend ---- */ -#define SIM_FLASH_SIZE (256 * 1024) +/* ---- Simulated flash and slot geometry ---- */ + +#define SIM_FLASH_SIZE (128 * 1024) static uint8_t sim_flash[SIM_FLASH_SIZE]; -#define SLOT_A_OFFSET 0x10000 -#define SLOT_B_OFFSET 0x30000 -#define SLOT_REC_OFFSET 0x20000 +#define SLOT_A_ADDR 0x4000u +#define SLOT_B_ADDR 0x14000u +#define SLOT_SIZE 0x8000u /* 32KB, identical for both slots */ -static eos_slot_state_t slot_states[3] = { - EOS_SLOT_STATE_EMPTY, EOS_SLOT_STATE_EMPTY, EOS_SLOT_STATE_EMPTY -}; -static uint32_t slot_versions[3] = {0, 0, 0}; -static uint8_t slot_boot_attempts[3] = {0, 0, 0}; -static bool slot_confirmed[3] = {false, false, false}; - -/* ---- Stub implementations ---- */ -int eos_slot_scan_all(void) { - int valid = 0; - for (int i = 0; i < 3; i++) { - if (slot_states[i] == EOS_SLOT_STATE_VALID || - slot_states[i] == EOS_SLOT_STATE_CONFIRMED) - valid++; - } - erase_result = EOS_OK; - erased_addr = 0; - erased_size = 0; -} +/* ---- Scriptable image-verification results, indexed by slot ---- */ -uint32_t eos_hal_slot_addr(eos_slot_t slot) -{ - if (slot == EOS_SLOT_A) return SLOT_A_ADDR; - if (slot == EOS_SLOT_B) return SLOT_B_ADDR; - return 0; -} +static int parse_result[2]; +static int integrity_result[2]; +static int signature_result[2]; +static uint32_t slot_version[2]; -uint32_t eos_hal_slot_size(eos_slot_t slot) -{ - return slot <= EOS_SLOT_B ? SLOT_SIZE : 0; -} +/* ---- Observed flash erase behaviour ---- */ + +static int erase_result; +static uint32_t erased_addr; +static size_t erased_size; -int eos_hal_flash_erase(uint32_t addr, size_t len) +/** Map a flash address back to the slot that starts there, or -1. */ +static int slot_index(uint32_t addr) { - erased_addr = addr; - erased_size = len; - return erase_result; + if (addr == SLOT_A_ADDR) return EOS_SLOT_A; + if (addr == SLOT_B_ADDR) return EOS_SLOT_B; + return -1; } +/* ---- Image verification mocks (override eboot_core's real ones) ---- + * + * verify_slot() passes the parsed header straight to the integrity and + * signature checks, so the mocks stash the slot index in reserved[0] on + * parse and read it back to decide which scripted result to return. */ + int eos_image_parse_header(uint32_t addr, eos_image_header_t *out) { int slot = slot_index(addr); if (slot < 0 || !out) return EOS_ERR_INVALID; if (parse_result[slot] != EOS_OK) return parse_result[slot]; -int eos_slot_erase(eos_slot_t slot) { - if (slot > EOS_SLOT_RECOVERY) return EOS_ERR_INVALID; - slot_states[slot] = EOS_SLOT_STATE_EMPTY; - slot_versions[slot] = 0; - slot_boot_attempts[slot] = 0; - slot_confirmed[slot] = false; + memset(out, 0, sizeof(*out)); + out->magic = EOS_IMG_MAGIC; + out->hdr_version = EOS_IMAGE_HDR_VERSION; + out->hdr_size = (uint16_t)sizeof(eos_image_header_t); + out->image_size = 0x100; + out->image_version = slot_version[slot]; + out->reserved[0] = (uint8_t)slot; return EOS_OK; } -int eos_slot_mark_booting(eos_slot_t slot) { - if (slot > EOS_SLOT_RECOVERY) return EOS_ERR_INVALID; - if (slot_boot_attempts[slot] < 255) { - slot_boot_attempts[slot]++; - } - return EOS_OK; -} - -int eos_slot_confirm(eos_slot_t slot) { - if (slot > EOS_SLOT_RECOVERY) return EOS_ERR_INVALID; - slot_boot_attempts[slot] = 0; - slot_confirmed[slot] = true; - if (slot_states[slot] == EOS_SLOT_STATE_VALID) { - slot_states[slot] = EOS_SLOT_STATE_CONFIRMED; - } - return EOS_OK; +int eos_image_verify_integrity(const eos_image_header_t *hdr, uint32_t addr) +{ + (void)addr; + if (!hdr || hdr->reserved[0] > EOS_SLOT_B) return EOS_ERR_INVALID; + return integrity_result[hdr->reserved[0]]; } -bool eos_slot_needs_rollback(eos_slot_t slot, uint8_t max_attempts) { - if (slot > EOS_SLOT_RECOVERY || max_attempts == 0) return false; - return slot_boot_attempts[slot] >= max_attempts; +int eos_image_verify_signature(const eos_image_header_t *hdr) +{ + if (!hdr || hdr->reserved[0] > EOS_SLOT_B) return EOS_ERR_INVALID; + return signature_result[hdr->reserved[0]]; } -uint8_t eos_slot_get_boot_attempts(eos_slot_t slot) { - if (slot > EOS_SLOT_RECOVERY) return 0; - return slot_boot_attempts[slot]; -} +/* ---- Simulated board ---- */ -/* ---- Helper ---- */ -static void reset_slots(void) { - memset(sim_flash, 0xFF, SIM_FLASH_SIZE); - for (int i = 0; i < 3; i++) { - slot_states[i] = EOS_SLOT_STATE_EMPTY; - slot_versions[i] = 0; - slot_boot_attempts[i] = 0; - slot_confirmed[i] = false; - } +static int sim_flash_read(uint32_t addr, void *buf, size_t len) +{ + if (addr + len > SIM_FLASH_SIZE) return EOS_ERR_FLASH; + memcpy(buf, &sim_flash[addr], len); + return EOS_OK; } -/* ---- Tests ---- */ -static void test_scan_no_valid_slots(void) { - reset_slots(); - int count = eos_slot_scan_all(); - assert(count == 0); - PASS("scan_no_valid_slots"); +static int sim_flash_write(uint32_t addr, const void *buf, size_t len) +{ + if (addr + len > SIM_FLASH_SIZE) return EOS_ERR_FLASH; + memcpy(&sim_flash[addr], buf, len); + return EOS_OK; } -int eos_image_verify_signature(const eos_image_header_t *hdr) +static int sim_flash_erase(uint32_t addr, size_t len) { - if (!hdr || hdr->reserved[0] > EOS_SLOT_B) return EOS_ERR_INVALID; - return signature_result[hdr->reserved[0]]; + erased_addr = addr; + erased_size = len; + if (erase_result != EOS_OK) return erase_result; + if (addr + len > SIM_FLASH_SIZE) return EOS_ERR_FLASH; + memset(&sim_flash[addr], 0xFF, len); + return EOS_OK; } +static uint32_t sim_tick = 0; +static uint32_t sim_get_tick(void) { return sim_tick++; } +static void sim_noop(void) {} +static void sim_noop_u32(uint32_t x) { (void)x; } +static void sim_jump(uint32_t addr) { (void)addr; } +static eos_reset_reason_t sim_reset_reason(void) { return EOS_RESET_POWER_ON; } +static bool sim_recovery_pin(void) { return false; } +static void sim_system_reset(void) {} + +static const eos_board_ops_t sim_ops = { + .flash_base = 0, + .flash_size = SIM_FLASH_SIZE, + .slot_a_addr = SLOT_A_ADDR, + .slot_a_size = SLOT_SIZE, + .slot_b_addr = SLOT_B_ADDR, + .slot_b_size = SLOT_SIZE, + .recovery_addr = 0, + .recovery_size = 0, + .bootctl_addr = 0, + .bootctl_backup_addr = 0x1000, + .log_addr = 0x2000, + .app_vector_offset = 0, + .flash_read = sim_flash_read, + .flash_write = sim_flash_write, + .flash_erase = sim_flash_erase, + .watchdog_init = sim_noop_u32, + .watchdog_feed = sim_noop, + .get_reset_reason = sim_reset_reason, + .system_reset = sim_system_reset, + .recovery_pin_asserted = sim_recovery_pin, + .jump = sim_jump, + .uart_init = NULL, + .uart_send = NULL, + .uart_recv = NULL, + .get_tick_ms = sim_get_tick, + .disable_interrupts = sim_noop, + .enable_interrupts = sim_noop, + .deinit_peripherals = sim_noop, +}; + +/* ---- Test harness ---- */ + +static int tests_run = 0; +static int tests_passed = 0; + +#define TEST(name) \ + static void name(void); \ + static void run_##name(void) { \ + memset(sim_flash, 0xFF, sizeof(sim_flash)); \ + sim_tick = 0; \ + for (int i = 0; i < 2; i++) { \ + parse_result[i] = EOS_ERR_NO_IMAGE; \ + integrity_result[i] = EOS_OK; \ + signature_result[i] = EOS_OK; \ + slot_version[i] = 0; \ + } \ + erase_result = EOS_OK; \ + erased_addr = 0; \ + erased_size = 0; \ + eos_hal_init(&sim_ops); \ + printf(" %-55s ", #name); \ + name(); \ + tests_passed++; \ + printf("[PASS]\n"); \ + } \ + static void name(void) + +#define ASSERT(cond) do { \ + if (!(cond)) { \ + printf("[FAIL] %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + exit(1); \ + } \ +} while(0) + +/** Script a slot so that all three verification stages succeed. */ static void make_valid(eos_slot_t slot, uint32_t version) { parse_result[slot] = EOS_OK; slot_version[slot] = version; } -static void test_scan_no_valid_slots(void) +/* ---- Tests ---- */ + +TEST(test_scan_no_valid_slots) { ASSERT(eos_slot_scan_all() == 0); ASSERT(eos_slot_get_state(EOS_SLOT_A) == EOS_SLOT_STATE_EMPTY); ASSERT(eos_slot_get_state(EOS_SLOT_B) == EOS_SLOT_STATE_EMPTY); } -static void test_scan_one_valid_slot(void) +TEST(test_scan_one_valid_slot) { make_valid(EOS_SLOT_A, EOS_VERSION_MAKE(1, 2, 3)); @@ -155,7 +217,7 @@ static void test_scan_one_valid_slot(void) ASSERT(header.image_version == EOS_VERSION_MAKE(1, 2, 3)); } -static void test_scan_two_valid_slots(void) +TEST(test_scan_two_valid_slots) { make_valid(EOS_SLOT_A, EOS_VERSION_MAKE(1, 0, 0)); make_valid(EOS_SLOT_B, EOS_VERSION_MAKE(2, 0, 0)); @@ -166,7 +228,7 @@ static void test_scan_two_valid_slots(void) ASSERT(eos_slot_get_version(EOS_SLOT_B) == EOS_VERSION_MAKE(2, 0, 0)); } -static void test_verification_failures_are_invalid(void) +TEST(test_verification_failures_are_invalid) { make_valid(EOS_SLOT_A, EOS_VERSION_MAKE(1, 0, 0)); make_valid(EOS_SLOT_B, EOS_VERSION_MAKE(2, 0, 0)); @@ -179,7 +241,7 @@ static void test_verification_failures_are_invalid(void) ASSERT(eos_slot_get_header(EOS_SLOT_A, NULL) == EOS_ERR_INVALID); } -static void test_invalid_slot_is_rejected(void) +TEST(test_invalid_slot_is_rejected) { ASSERT(!eos_slot_is_valid(EOS_SLOT_RECOVERY)); ASSERT(eos_slot_get_state(EOS_SLOT_RECOVERY) == EOS_SLOT_STATE_EMPTY); @@ -188,7 +250,7 @@ static void test_invalid_slot_is_rejected(void) ASSERT(eos_slot_erase(EOS_SLOT_RECOVERY) == EOS_ERR_INVALID); } -static void test_erase_updates_state_only_on_success(void) +TEST(test_erase_updates_state_only_on_success) { make_valid(EOS_SLOT_A, EOS_VERSION_MAKE(1, 0, 0)); ASSERT(eos_slot_scan_all() == 1); @@ -205,65 +267,52 @@ static void test_erase_updates_state_only_on_success(void) ASSERT(eos_slot_get_version(EOS_SLOT_A) == 0); } -static void test_boot_attempts_and_rollback(void) { - reset_slots(); - slot_states[EOS_SLOT_A] = EOS_SLOT_STATE_VALID; - assert(eos_slot_get_boot_attempts(EOS_SLOT_A) == 0); - assert(!eos_slot_needs_rollback(EOS_SLOT_A, 3)); - - // Attempt 1 - assert(eos_slot_mark_booting(EOS_SLOT_A) == EOS_OK); - assert(eos_slot_get_boot_attempts(EOS_SLOT_A) == 1); - assert(!eos_slot_needs_rollback(EOS_SLOT_A, 3)); - - // Attempt 2 - assert(eos_slot_mark_booting(EOS_SLOT_A) == EOS_OK); - assert(eos_slot_get_boot_attempts(EOS_SLOT_A) == 2); - assert(!eos_slot_needs_rollback(EOS_SLOT_A, 3)); - - // Attempt 3 (hits max allowed 3) - assert(eos_slot_mark_booting(EOS_SLOT_A) == EOS_OK); - assert(eos_slot_get_boot_attempts(EOS_SLOT_A) == 3); - assert(eos_slot_needs_rollback(EOS_SLOT_A, 3)); - - // Confirm slot (resets boot attempts and confirms) - assert(eos_slot_confirm(EOS_SLOT_A) == EOS_OK); - assert(eos_slot_get_boot_attempts(EOS_SLOT_A) == 0); - assert(!eos_slot_needs_rollback(EOS_SLOT_A, 3)); - assert(eos_slot_get_state(EOS_SLOT_A) == EOS_SLOT_STATE_CONFIRMED); - - // Invalid slot handles - assert(eos_slot_mark_booting((eos_slot_t)0xFE) == EOS_ERR_INVALID); - assert(eos_slot_confirm((eos_slot_t)0xFE) == EOS_ERR_INVALID); - assert(eos_slot_get_boot_attempts((eos_slot_t)0xFE) == 0); - assert(!eos_slot_needs_rollback((eos_slot_t)0xFE, 3)); - PASS("boot_attempts_and_rollback"); +TEST(test_boot_attempts_and_rollback) +{ + make_valid(EOS_SLOT_A, EOS_VERSION_MAKE(1, 0, 0)); + ASSERT(eos_slot_scan_all() == 1); + + ASSERT(eos_slot_get_boot_attempts(EOS_SLOT_A) == 0); + ASSERT(!eos_slot_needs_rollback(EOS_SLOT_A, 3)); + + for (uint8_t attempt = 1; attempt <= 2; attempt++) { + ASSERT(eos_slot_mark_booting(EOS_SLOT_A) == EOS_OK); + ASSERT(eos_slot_get_boot_attempts(EOS_SLOT_A) == attempt); + ASSERT(!eos_slot_needs_rollback(EOS_SLOT_A, 3)); + } + + /* The third attempt reaches max_attempts, which is what triggers rollback. */ + ASSERT(eos_slot_mark_booting(EOS_SLOT_A) == EOS_OK); + ASSERT(eos_slot_get_boot_attempts(EOS_SLOT_A) == 3); + ASSERT(eos_slot_needs_rollback(EOS_SLOT_A, 3)); + + /* Confirming clears the attempt count and promotes VALID to CONFIRMED. */ + ASSERT(eos_slot_confirm(EOS_SLOT_A) == EOS_OK); + ASSERT(eos_slot_get_boot_attempts(EOS_SLOT_A) == 0); + ASSERT(!eos_slot_needs_rollback(EOS_SLOT_A, 3)); + ASSERT(eos_slot_get_state(EOS_SLOT_A) == EOS_SLOT_STATE_CONFIRMED); + + /* max_attempts == 0 means "no attempt limit", never a rollback. */ + ASSERT(!eos_slot_needs_rollback(EOS_SLOT_A, 0)); + + /* Out-of-range slot handles are rejected rather than indexed. */ + ASSERT(eos_slot_mark_booting((eos_slot_t)0xFE) == EOS_ERR_INVALID); + ASSERT(eos_slot_confirm((eos_slot_t)0xFE) == EOS_ERR_INVALID); + ASSERT(eos_slot_get_boot_attempts((eos_slot_t)0xFE) == 0); + ASSERT(!eos_slot_needs_rollback((eos_slot_t)0xFE, 3)); } -int main(void) { - printf("=== eboot Slot Manager Tests ===\n"); - test_scan_no_valid_slots(); - test_scan_one_valid_slot(); - test_scan_two_valid_slots(); - test_scan_all_slots_valid(); - test_slot_is_valid_empty(); - test_slot_is_valid_with_image(); - test_slot_is_valid_confirmed(); - test_slot_is_valid_invalid_state(); - test_slot_get_version_empty(); - test_slot_get_version_with_image(); - test_slot_get_state_empty(); - test_slot_get_state_valid(); - test_slot_get_state_testing(); - test_slot_erase(); - test_slot_erase_already_empty(); - test_slot_get_header_valid(); - test_slot_get_header_empty(); - test_slot_get_header_null(); - test_version_macro_encoding(); - test_version_macro_max_values(); - test_slot_enum_values(); - test_boot_attempts_and_rollback(); - printf("\n=== ALL %d TESTS PASSED ===\n", passed); - return 0; +int main(void) +{ + printf("=== eBootloader: Slot Manager Tests ===\n\n"); + run_test_scan_no_valid_slots(); + run_test_scan_one_valid_slot(); + run_test_scan_two_valid_slots(); + run_test_verification_failures_are_invalid(); + run_test_invalid_slot_is_rejected(); + run_test_erase_updates_state_only_on_success(); + run_test_boot_attempts_and_rollback(); + tests_run = 7; + printf("\n%d/%d tests passed\n", tests_passed, tests_run); + return (tests_passed == tests_run) ? 0 : 1; }