From c9fe84c4b26c990cd956712da8c7b9a205d675cc Mon Sep 17 00:00:00 2001 From: Tanisha Panchal Date: Fri, 4 Sep 2026 00:42:14 +0530 Subject: [PATCH] Fix anti-rollback to use authenticated TLV counter --- core/fw_update.c | 18 ++++++++++++++---- stage1/jump_app.c | 22 ++++++++++++++++++---- tests/unit/test_tlv_auth.c | 37 +++++++++++++++++++++++++++++++++++-- 3 files changed, 67 insertions(+), 10 deletions(-) diff --git a/core/fw_update.c b/core/fw_update.c index 269349d..1e2157b 100644 --- a/core/fw_update.c +++ b/core/fw_update.c @@ -10,6 +10,7 @@ #include "eos_fw_update.h" #include "eos_hal.h" #include "eos_fwsvc.h" +#include "eos_rollback.h" #include int eos_fw_update_begin(eos_fw_update_ctx_t *ctx, eos_slot_t slot) @@ -184,10 +185,19 @@ int eos_fw_update_finalize(eos_fw_update_ctx_t *ctx, eos_upgrade_mode_t mode) } } - /* Anti-rollback check */ - extern int eos_image_check_rollback(uint32_t candidate_version); - int rb_rc = eos_image_check_rollback(ctx->header.image_version); - if (rb_rc != EOS_OK && rb_rc != EOS_ERR_NOT_SUPPORTED) { + /* Persistent floor: authenticated TLV security counter, not image_version. + * Finalize installs the image; it does not boot it, so the counter is + * verified here but not staged. */ + uint32_t img_counter = 0; + int rb_rc = eos_rollback_read_image_counter(ctx->target_addr, &img_counter); + if (rb_rc != EOS_OK) { + ctx->state = EOS_FW_STATE_ERROR; + ctx->last_error = rb_rc; + return rb_rc; + } + + rb_rc = eos_rollback_verify(img_counter); + if (rb_rc != EOS_OK) { ctx->state = EOS_FW_STATE_ERROR; ctx->last_error = rb_rc; return rb_rc; diff --git a/stage1/jump_app.c b/stage1/jump_app.c index fe8b3e2..e69a717 100644 --- a/stage1/jump_app.c +++ b/stage1/jump_app.c @@ -14,6 +14,7 @@ #include "eos_hal.h" #include "eos_bootctl.h" #include "eos_image.h" +#include "eos_rollback.h" #include "eos_mpu_boot.h" /* Forward declarations from boot_log */ @@ -56,13 +57,26 @@ int eboot_jump_to_app(eos_bootctl_t *bctl, eos_slot_t slot) return rc; } - /* Anti-rollback check */ - extern int eos_image_check_rollback(uint32_t candidate_version); - rc = eos_image_check_rollback(hdr.image_version); - if (rc != EOS_OK && rc != EOS_ERR_NOT_SUPPORTED) { + /* Persistent floor from the device monotonic counter. The value compared + * here is the authenticated EOS_TLV_MIN_SEC_VER, not image_version: + * firmware versions and OTP security counters are different scales, and + * comparing the former to the latter lets a high version / low TLV image + * walk past the floor. Same sequence as eos_secure_boot() step 5b. */ + uint32_t img_counter = 0; + rc = eos_rollback_read_image_counter(addr, &img_counter); + if (rc != EOS_OK) { eos_boot_log_append(EOS_LOG_IMAGE_INVALID, slot, rc); return rc; } + + rc = eos_rollback_verify(img_counter); + if (rc != EOS_OK) { + eos_boot_log_append(EOS_LOG_IMAGE_INVALID, slot, rc); + return rc; + } + + /* Raised only once the image is confirmed good; see eos_bootctl_confirm(). */ + eos_rollback_stage(img_counter); /* Increment boot attempts before jumping */ eos_bootctl_increment_attempts(bctl); diff --git a/tests/unit/test_tlv_auth.c b/tests/unit/test_tlv_auth.c index 28c0e41..be3b543 100644 --- a/tests/unit/test_tlv_auth.c +++ b/tests/unit/test_tlv_auth.c @@ -290,11 +290,43 @@ TEST(test_oversized_tlv_len_is_rejected) ASSERT(eos_rollback_read_image_counter(SLOT_A_ADDR, &counter) == EOS_ERR_INVALID); } +/* + * Regression: the production boot and update paths used to feed + * hdr.image_version into eos_image_check_rollback(), which compares that + * number to the OTP monotonic floor. Firmware versions (0x00MMmmpp) and + * TLV security counters are different scales. An old image with + * image_version = 0x00010000 and authenticated MIN_SEC_VER = 3 therefore + * passed a floor of 9, defeating the anti-rollback gate. + * + * The hardware-floor decision is eos_rollback_verify(tlv_counter). + */ +extern int eos_image_check_rollback(uint32_t candidate_version); + +TEST(test_hw_floor_uses_tlv_counter_not_image_version) +{ + build_image(3, true, true); + + eos_image_header_t hdr; + ASSERT(eos_image_parse_header(SLOT_A_ADDR, &hdr) == EOS_OK); + ASSERT(hdr.image_version == 0x00010000u); + + uint32_t counter = 0; + ASSERT(eos_rollback_read_image_counter(SLOT_A_ADDR, &counter) == EOS_OK); + ASSERT(counter == 3); + + sim_counter = 9; + + /* The old production check: image_version against the HW floor. */ + ASSERT(eos_image_check_rollback(hdr.image_version) == EOS_OK); + + /* The authenticated counter is below the floor and must be rejected. */ + ASSERT(eos_rollback_verify(counter) == EOS_ERR_ANTI_ROLLBACK); +} + int main(void) { printf("TLV authentication (anti-rollback counter)\n\n"); - tests_run = 8; run_test_authenticated_tlv_counter_is_read(); run_test_tampered_tlv_counter_is_rejected(); run_test_tamper_is_invisible_to_signature_and_integrity(); @@ -302,8 +334,9 @@ int main(void) run_test_image_without_tlv_area_still_reports_zero(); run_test_tlv_binding_fields_are_inside_the_signed_prefix(); run_test_oversized_tlv_len_is_rejected(); + run_test_hw_floor_uses_tlv_counter_not_image_version(); - tests_run = 7; + tests_run = 8; printf("\n%d/%d passed\n", tests_passed, tests_run); return tests_passed == tests_run ? 0 : 1; }