Fix OOB flash write and OOB stack read in recovery firmware-update path - #33
Open
simran1002 wants to merge 1 commit into
Open
Conversation
… read in YMODEM header parsing
simran1002
requested review from
hshanmug12,
maheshmunnangi and
srpatcha
as code owners
August 25, 2026 14:28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix: two out-of-bounds bugs in the recovery-mode firmware-update path
The issue
Two memory-safety bugs in the code that handles field firmware updates over
UART — the most attacker-facing surface in a bootloader, since it's reachable
by anyone who can talk to the recovery port, and it runs before any application
code:
1. Out-of-bounds flash write (
core/recovery.c,recovery_handle_write())The
WRITEcommand'soffsetfield comes straight off the wire and waschecked only against the write's own chunk size (
len > sizeof(buf)) — neveragainst the target slot's actual size.
eos_hal_flash_write()is a thinpass-through to raw flash writes with no bounds checking of its own. Result:
an authenticated recovery client (the command is auth-gated, but nothing more)
can write past a slot's boundary into the other slot, the boot-control
blocks, or the boot log — corrupting boot state or defeating A/B rollback
protection. This is a live path:
eos_recovery_enter()is called directly fromstage1/main.cwhenever no valid image is found.2. Out-of-bounds stack read (
core/fw_transport_uart.c,ymodem_receive())For YMODEM "block 0" (the filename/size header), only
block_size + 2bytes(130 of 1026) of the
blockstack buffer are actually filled byeos_hal_uart_recv(). Parsing then didstrlen(name)over that buffer — ifthe sender never sends a NUL within the received region,
strlenwalks offinto ~896 bytes of uninitialized stack looking for a zero byte. The
size-parsing loop right after it had the same problem. A malicious or just
buggy YMODEM sender can crash or hang the bootloader mid-update.
The fix
recovery_handle_write(): validate(uint64_t)offset + len <= eos_hal_slot_size(slot)before touching flash, NACK otherwise. Matches thebound-check the project's own
docs/security_review_checklist.mdalreadycalls out ("Flash read/write operations validate address ranges against
slot boundaries") but wasn't actually implemented here.
ymodem_receive(): replacestrlen()withstrnlen(name, block_size), andbound the size-digit scan to the actually-received region
(
block + block_size) instead of scanning until a non-digit byte turns upwherever it turns up.
Testing
Added
tests/unit/test_recovery.c— an end-to-end test that drives the realeos_recovery_enter()state machine through a simulated UART/flash/OTP, not amock of the handler function:
deterministic simulated RNG challenge + a simulated OTP shared secret,
computed with the same primitives the bootloader uses).
WRITEwithoffsetexactly at the slot boundary — asserts it'sNACKed and flash is untouched (fails before the fix: it would ACK and
attempt the out-of-bounds write).
WRITEimmediately after — asserts it stillsucceeds, proving the fix doesn't affect legitimate writes.
Registered in
tests/CMakeLists.txt. Full suite (Docker/Ubuntu 22.04, nolocal toolchain available):
ctest --test-dir build→ 12/12 passed(11 pre-existing + 1 new).
The
strnlen/scan-bound fix forfw_transport_uart.cdoesn't yet have adedicated test — see limitations below.
Limitations / considerations
ARM/RISC-V board targets under
boards/were not built or tested.strnlenfix specifically —it would need a UART transport test harness that doesn't currently exist
for
fw_transport_uart.c. Worth a follow-up.tests/fuzz/fuzz_recovery_protocol.creferences a function,
eos_recovery_parse_packet, that doesn't existanywhere in
core/recovery.c— that fuzz harness appears to be dead codethat has never actually compiled/linked. Flagging it since it's exactly the
kind of harness that should be exercising this code path.