Found by the automated review of #129 (finding 1, Critical); reproduced here with build.yml's flags and with toolchains/arm-none-eabi.cmake.
What
On every board build with a stage-1 linker script, eboot_firmware.bin is 0 bytes:
stage1/main.c exports eboot_main(), not a reset handler, and nothing in stage1/ provides a vector table;
boards/stm32f4/stm32f4_stage1.ld has no ENTRY() and keeps no section (the stage-0 script has KEEP(*(.isr_vector)));
- the target links with
-nostartfiles and -Wl,--gc-sections, so the linker has nothing to anchor: arm-none-eabi-objdump -h eboot_firmware.elf → .text 00000000.
tools/embed_stage1_hash.py has no minimum-size check — size = os.path.getsize(input_path) is used verbatim — so it emits stage1_expected_size = 0u and stage1_expected_hash = e3b0c442…b855 (the SHA-256 of the empty string) and prints both as a normal build line. On the device, stage0/jump_stage1.c's while (off < 0) runs zero iterations, computed equals the expected digest trivially, EOS_LOG_IMAGE_VALID is appended, and ops->jump(stage1_addr) transfers control to whatever is in flash. The first link of the secure boot chain reports success without having run, and CI is green over it.
Alongside: stage0/reset_entry.c's vector table stops at UsageFault (7 entries), while board_early_init() on stm32f4 enables the SysTick interrupt, whose vector is entry 15 — so one millisecond after reset the core fetches its SysTick handler address from the code that follows the table.
Expected
Three independent guards: the stage-1 link anchored (a reset entry owning the vector table, ENTRY(Reset_Handler) and a kept vector section in every stage-1 linker script); the embed tool refusing an empty or implausibly small image; and stage-0 refusing stage1_expected_size == 0 before the hash loop. Both vector tables reaching SysTick.
Found by the automated review of #129 (finding 1, Critical); reproduced here with
build.yml's flags and withtoolchains/arm-none-eabi.cmake.What
On every board build with a stage-1 linker script,
eboot_firmware.binis 0 bytes:stage1/main.cexportseboot_main(), not a reset handler, and nothing instage1/provides a vector table;boards/stm32f4/stm32f4_stage1.ldhas noENTRY()and keeps no section (the stage-0 script hasKEEP(*(.isr_vector)));-nostartfilesand-Wl,--gc-sections, so the linker has nothing to anchor:arm-none-eabi-objdump -h eboot_firmware.elf→.text 00000000.tools/embed_stage1_hash.pyhas no minimum-size check —size = os.path.getsize(input_path)is used verbatim — so it emitsstage1_expected_size = 0uandstage1_expected_hash = e3b0c442…b855(the SHA-256 of the empty string) and prints both as a normal build line. On the device,stage0/jump_stage1.c'swhile (off < 0)runs zero iterations,computedequals the expected digest trivially,EOS_LOG_IMAGE_VALIDis appended, andops->jump(stage1_addr)transfers control to whatever is in flash. The first link of the secure boot chain reports success without having run, and CI is green over it.Alongside:
stage0/reset_entry.c's vector table stops at UsageFault (7 entries), whileboard_early_init()on stm32f4 enables the SysTick interrupt, whose vector is entry 15 — so one millisecond after reset the core fetches its SysTick handler address from the code that follows the table.Expected
Three independent guards: the stage-1 link anchored (a reset entry owning the vector table,
ENTRY(Reset_Handler)and a kept vector section in every stage-1 linker script); the embed tool refusing an empty or implausibly small image; and stage-0 refusingstage1_expected_size == 0before the hash loop. Both vector tables reaching SysTick.