Malicious firmware can reuse protected bootloader code to bypass the callgate and expose firewall secrets
The callgate preserves and later trusts the firmware-controlled incoming stack pointer and link register. At entry, callgate_entry0 saves the caller's sp and lr, switches to the bootloader stack, and calls the dispatcher:
// stm32/mk4-bootloader/startup.S:140-150
mov r10, sp
mov sp, r9
push {r10, lr}
bl firewall_dispatch
pop {r10, lr}
mov sp, r10
The dispatcher sets the firewall pre-arm bit before returning (stm32/mk4-bootloader/dispatch.c:700-703). The assembly then wipes bootloader SRAM and performs an unchecked indirect branch through the saved caller LR:
// stm32/mk4-bootloader/startup.S:152-163
wipe_loop2:
str r0, [r9], +4
cmp r9, r10
bne wipe_loop2
bx lr
Hostile firmware is not required to enter with a linking branch. It can set lr to any odd address inside the protected code segment, set sp to attacker-controlled firmware SRAM, and use bx to enter the always-open callgate at callgate_entry0. Because the chosen return remains inside the protected segment, pre-arm does not close the firewall. ST's RM0432 Rev. 9, Firewall control register (FW_CR), FPA bit specifies that FPA=1 closes the firewall when code executes outside the protected segment; it does not close it for an intra-segment branch. The bundled HAL documents the same rule (stm32/mk4-bootloader/stm32l4xx_hal_firewall.c:228-243). The linker places dispatch.o, main.o, selected HAL objects, all remaining .text*, and all rodata after the firewall boundary (stm32/mk4-bootloader/link-script.ld:37-49), providing a large protected ROP/code-reuse surface.
After mov sp, r10, the attacker-selected protected target executes with an attacker-controlled stack and attacker-controlled callee-saved registers (r4-r8 and r11 survive the dispatcher ABI). A targeted Capstone disassembly of the repository's Mk4 releases/3.3.0/bootloader.bin confirmed that the callgate pointer is 0x08000305, the unchecked return is at 0x08000342, and the protected image contains numerous stack-loading return gadgets, including pop {r0, r3, r5, r7, pc} at 0x080024d8 and pop {r2, r3, r4, r6, r7, pc} at 0x080023e2, as well as the byte-copy routine at 0x0800d688. These confirmed primitives provide a protected ROP/code-reuse path capable of loading copy arguments and moving protected NVROM bytes to firmware-readable SRAM. The evidence demonstrates the control-flow bypass and required gadgets; it does not include a complete hardware extraction chain. The bypass avoids the intended callgate method switch and its pointer, PIN-state, and output-range validation.
Impact
Replaceable malicious firmware can set its stack to a crafted ROP chain, set LR to a protected Thumb gadget/function, and enter the callgate using bx rather than blx. When the legitimate dispatch operation completes, callgate_entry0 restores the malicious stack and branches to the protected address without closing the firewall. The chain can invoke protected routines directly and copy non-volatile protected data, including the pairing secret or MCU key material, into firmware-readable SRAM before finally branching outside the segment. This defeats the firewall's core isolation property and can expose secrets used to authenticate secure-element operations and protect wallet state.
Recommendation
Do not return through firmware-supplied LR or restore firmware-supplied SP while protected execution is still open. Validate that both values describe the exact expected caller context and force the final transition through a fixed, minimal exit trampoline that cannot branch within the protected segment. The exit path should close the firewall atomically before any attacker-controlled control-flow state is consumed.