Repository navigation
fix(fw_update): refuse to erase the active slot in eos_fw_update_begin #156
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,13 +12,25 @@ | |
| #include "eos_fwsvc.h" | ||
| #include "eos_rollback.h" | ||
| #include "eos_image_tlv.h" | ||
| #include "eos_bootctl.h" | ||
| #include <string.h> | ||
|
|
||
| int eos_fw_update_begin(eos_fw_update_ctx_t *ctx, eos_slot_t slot) | ||
| { | ||
| if (!ctx) return EOS_ERR_INVALID; | ||
| if (slot != EOS_SLOT_A && slot != EOS_SLOT_B) return EOS_ERR_INVALID; | ||
|
|
||
| /* Never erase the slot the device is running from. The boot control | ||
| * block names it; when neither copy is valid eos_bootctl_load() falls | ||
| * back to the factory defaults (Slot A active), which is the slot the | ||
| * bootloader would boot, so that slot is protected too. Any other load | ||
| * failure means the active slot is unknown, and erasing either slot | ||
| * could destroy the running image, so the update is refused. */ | ||
| eos_bootctl_t bctl; | ||
| int load_rc = eos_bootctl_load(&bctl); | ||
|
Comment on lines
+29
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3. Updates can fault reading address zero The new eos_bootctl_load() call makes update initiation perform a backup read whenever the primary control block is invalid, even on boards with no backup address configured. On the RISC-V virtual board, corruption of the primary after boot sends that read to address zero, which its flash reader dereferences before the update can return a refusal or use defaults. Agent Prompt
|
||
| if (load_rc != EOS_OK && load_rc != EOS_ERR_CRC) return load_rc; | ||
|
Comment on lines
+30
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Preserve read failures separately from corrupt-copy defaults, and test that they prevent every erase. Prompt To Fix With AIThis is a comment left during a code review.
Path: core/fw_update.c
Line: 30-31
Comment:
**Failed reads allow an erase**
`eos_bootctl_load()` returns `EOS_ERR_CRC` not only for corrupt copies, but also when both flash reads fail. It then supplies defaults naming A as active. If a device boots B and those reads later fail, `eos_fw_update_begin(..., EOS_SLOT_B)` accepts the defaults and erases the running B image instead of refusing the update.
Preserve read failures separately from corrupt-copy defaults, and test that they prevent every erase.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Refuse updates when the running slot is unknown.
|
||
| if ((eos_slot_t)bctl.active_slot == slot) return EOS_ERR_INVALID; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Keep the running slot separate from the next boot target, and test this sequence. Prompt To Fix With AIThis is a comment left during a code review.
Path: core/fw_update.c
Line: 32
Comment:
**Running firmware remains unprotected**
`active_slot` can name the next boot target rather than the running image. While running A, finishing an update to B with `EOS_UPGRADE_PERMANENT` saves `active_slot = B` without rebooting. A second `eos_fw_update_begin(..., EOS_SLOT_A)` passes this check and erases the running A image.
Keep the running slot separate from the next boot target, and test this sequence.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1. A second update can erase running firmware eos_fw_update_begin() uses the persisted active_slot as the identity of the currently executing image, even though permanent upgrades, failed boot-selection saves, and CRC fallback can leave that value out of sync with execution. A second update before reset can therefore target the still-running old slot after a permanent upgrade, target a newly selected slot after its save fails, or target Slot B when invalid control copies cause the loader to default to A; each path reaches the erase. Agent Prompt
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Protect the running slot after a permanent upgrade.
|
||
|
|
||
| memset(ctx, 0, sizeof(*ctx)); | ||
| ctx->target_slot = slot; | ||
| ctx->target_addr = eos_hal_slot_addr(slot); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -389,6 +389,54 @@ TEST(test_finalize_accepts_tlv_counter_equal_to_floor) | |
| ASSERT(eos_fw_update_finalize(&ctx, EOS_UPGRADE_TEST) == EOS_OK); | ||
| } | ||
|
|
||
| /* ---- Active-slot protection ---- | ||
| * | ||
| * begin() erases the whole target slot before a byte of the new image | ||
| * arrives. Pointing it at the slot the device is running from destroyed | ||
| * the running firmware; it must refuse and leave that slot untouched. | ||
| */ | ||
|
|
||
| static void persist_bootctl_active(eos_slot_t active) | ||
| { | ||
| eos_bootctl_t bctl; | ||
| eos_bootctl_init_defaults(&bctl); | ||
| bctl.active_slot = active; | ||
| ASSERT(eos_bootctl_save(&bctl) == EOS_OK); | ||
| } | ||
|
|
||
| TEST(test_begin_refuses_active_slot_and_leaves_it_intact) | ||
| { | ||
| eos_fw_update_ctx_t ctx; | ||
|
|
||
| persist_bootctl_active(EOS_SLOT_B); | ||
| memset(&sim_flash[SIM_SLOT_B_ADDR], 0x5A, SIM_SLOT_B_SIZE); | ||
|
|
||
| ASSERT(eos_fw_update_begin(&ctx, EOS_SLOT_B) == EOS_ERR_INVALID); | ||
| ASSERT(sim_flash[SIM_SLOT_B_ADDR] == 0x5A); | ||
| ASSERT(sim_flash[SIM_SLOT_B_ADDR + SIM_SLOT_B_SIZE - 1] == 0x5A); | ||
| } | ||
|
|
||
| TEST(test_begin_accepts_inactive_slot) | ||
| { | ||
| eos_fw_update_ctx_t ctx; | ||
|
|
||
| persist_bootctl_active(EOS_SLOT_A); | ||
| ASSERT(eos_fw_update_begin(&ctx, EOS_SLOT_B) == EOS_OK); | ||
| ASSERT(eos_fw_update_get_state(&ctx) == EOS_FW_STATE_HEADER); | ||
| } | ||
|
|
||
| TEST(test_begin_refuses_default_active_slot_without_bootctl) | ||
| { | ||
| eos_fw_update_ctx_t ctx; | ||
|
|
||
| /* No valid boot control block: the bootloader boots its defaults, | ||
| * Slot A, so Slot A is the running image and must not be erased. */ | ||
| memset(&sim_flash[SIM_SLOT_A_ADDR], 0x5A, SIM_SLOT_A_SIZE); | ||
|
|
||
| ASSERT(eos_fw_update_begin(&ctx, EOS_SLOT_A) == EOS_ERR_INVALID); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use a nonzero Slot A address in this test.
|
||
| ASSERT(sim_flash[SIM_SLOT_A_ADDR] == 0x5A); | ||
| } | ||
|
|
||
| /* ---- Direct reads: eos_rollback_read_image_counter ---- | ||
| * | ||
| * The finalize tests above exercise the counter reader indirectly, | ||
|
|
@@ -469,6 +517,9 @@ int main(void) | |
| run_test_bytes_wanted_tracks_the_remaining_container(); | ||
| run_test_trailing_byte_is_rejected_the_same_across_chunk_boundaries(); | ||
| run_test_finalize_accepts_tlv_counter_equal_to_floor(); | ||
| run_test_begin_refuses_active_slot_and_leaves_it_intact(); | ||
| run_test_begin_accepts_inactive_slot(); | ||
| run_test_begin_refuses_default_active_slot_without_bootctl(); | ||
| run_test_read_image_counter_returns_tlv_counter(); | ||
| run_test_read_image_counter_no_tlv_reports_zero(); | ||
| run_test_read_image_counter_rejects_null_out(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2. The update file mixes crlf and lf
📘 Rule violation⚙ MaintainabilityAgent Prompt
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation toolsDismiss ↗ | View ↗