feat(core): add a boot command that enters the bootloader ready to talk
What changed, and why it matters
This commit adds a new internal reboot path that lets the Trezor Core firmware restart the device into the bootloader and immediately activate the USB/wire connection, skipping the usual intro screen that normally requires a finger tap. The change is intentionally limited: it does not approve any firmware installation, downgrade, or other security-sensitive action; it only changes which screen the bootloader shows first. It is exposed to unprivileged firmware through both a normal system call and a secure-monitor call, but it is not yet reachable from a host computer over USB. The main security consideration is that it slightly enlarges the attack surface for reboot-based attacks, though the commit message and code comments explicitly describe it as authorization-free and safe compared to the existing auto-upgrade reboot command.
Review whether any future host-facing protobuf change that exposes this command enforces appropriate authentication and user confirmation, since the current patch deliberately does not. Confirm that SCREEN_WAIT_FOR_HOST does not perform any privileged operation beyond interface initialization, and ensure that the new SMCALL/SYSCALL numbers cannot be invoked by untrusted application code if the firmware sandbox is ever compromised. No immediate patch is indicated by the diff alone.
Security signals we found
New bootloader boot command that skips the user-tap intro screen and brings up wire interfaces immediately
Exposed to unprivileged firmware via both syscall and secure-monitor call bridges
Explicitly described by the author as carrying no authorization or consent material
Contrast with BOOT_COMMAND_INSTALL_UPGRADE, which is restricted from firmware to prevent bypass of the weaker boot gate
Not yet exposed over the host wire protocol, limiting external reach
Motivating use case is self-invalidated firmware that needs host communication without a user tap
Evidence from the diff
The patch introduces BOOT_COMMAND_STOP_AND_CONNECT, SMCALL_REBOOT_AND_CONNECT, and SYSCALL_REBOOT_AND_CONNECT. When this boot command is set, bootloader_main() stays in the bootloader and passes connect_to_host=sectrue to workflow_bootloader(), which starts the state machine at SCREEN_WAIT_FOR_HOST instead of SCREEN_INTRO. SCREEN_WAIT_FOR_HOST is the screen that calls workflow_ifaces_init(), so the wire interfaces come up without a user tap. The reboot_and_connect() helper is added to both the STM32 and Unix bootutils implementations and is dispatched through both the syscall and smcall handlers. The commit message emphasizes that this command carries no consent material and authorizes nothing, unlike BOOT_COMMAND_INSTALL_UPGRADE, which is deliberately not exposed to unprivileged firmware. The new command is not exposed over the wire yet; a host-initiated RebootToBootloader still lands on the menu screen.
Changed components
core/embed/projects/bootloader/main.ccore/embed/projects/bootloader/workflow/wf_bootloader.ccore/embed/projects/bootloader/workflow/workflow.hcore/embed/sys/smcall/stm32/smcall_dispatch.ccore/embed/sys/smcall/stm32/smcall_numbers.hcore/embed/sys/smcall/stm32/smcall_stubs.ccore/embed/sys/startup/inc/sys/bootargs.hcore/embed/sys/startup/inc/sys/bootutils.hcore/embed/sys/startup/stm32/bootutils.ccore/embed/sys/startup/unix/bootutils.ccore/embed/sys/syscall/inc/sys/syscall_numbers.hcore/embed/sys/syscall/stm32/syscall_dispatch.ccore/embed/sys/syscall/stm32/syscall_stubs.cInspect captured patch +66 / −3
### core/embed/projects/bootloader/main.c
@@ -556,6 +556,9 @@ int bootloader_main(void) {
#endif
volatile secbool auto_upgrade = secfalse;
+ // Start the bootloader workflow on the connect screen instead of the intro
+ // (BOOT_COMMAND_STOP_AND_CONNECT).
+ volatile secbool connect_to_host = secfalse;
fw_check_info_t fw;
fw_check(&fw);
@@ -570,6 +573,13 @@ int bootloader_main(void) {
// firmware requested to stay in bootloader
stay_in_bootloader = sectrue;
break;
+ case BOOT_COMMAND_STOP_AND_CONNECT:
+ // As STOP_AND_WAIT, but a host is already waiting: start on the connect
+ // screen so the wire interfaces come up without a tap first. Carries no
+ // authorization -- it only selects the entry screen.
+ stay_in_bootloader = sectrue;
+ connect_to_host = sectrue;
+ break;
case BOOT_COMMAND_INSTALL_UPGRADE:
if (fw.firmware_present == sectrue) {
// continue without user interaction
@@ -640,7 +650,7 @@ int bootloader_main(void) {
if (auto_upgrade == sectrue && fw.firmware_present == sectrue) {
result = workflow_auto_update(&fw);
} else {
- result = workflow_bootloader(&fw);
+ result = workflow_bootloader(&fw, connect_to_host);
}
} else {
result = workflow_empty_device();
### core/embed/projects/bootloader/workflow/wf_bootloader.c
@@ -201,9 +201,16 @@ static screen_t handle_wait_for_host(const fw_check_info_t* fw,
return next_screen;
}
-workflow_result_t workflow_bootloader(const fw_check_info_t* fw) {
+workflow_result_t workflow_bootloader(const fw_check_info_t* fw,
+ secbool connect_to_host) {
ui_set_initial_setup(false);
+ // Only the entry point differs: the host-waiting screen is the one that
+ // initializes the wire interfaces, so a caller that knows a host is already
+ // connected can skip the tap the intro screen would otherwise require.
screen_t screen = SCREEN_INTRO;
+ if (sectrue == connect_to_host) {
+ screen = SCREEN_WAIT_FOR_HOST;
+ }
workflow_result_t final_res = WF_ERROR_FATAL;
while (screen != SCREEN_DONE) {
### core/embed/projects/bootloader/workflow/workflow.h
@@ -44,7 +44,11 @@ workflow_result_t workflow_get_features(protob_io_t *iface,
workflow_result_t workflow_menu(const fw_check_info_t *fw, protob_ios_t *ios);
-workflow_result_t workflow_bootloader(const fw_check_info_t *fw);
+// `connect_to_host` starts on the wait-for-host screen (which brings up the
+// wire interfaces) rather than the intro screen; see
+// BOOT_COMMAND_STOP_AND_CONNECT.
+workflow_result_t workflow_bootloader(const fw_check_info_t *fw,
+ secbool connect_to_host);
workflow_result_t workflow_empty_device(void);
### core/embed/sys/smcall/stm32/smcall_dispatch.c
@@ -100,6 +100,10 @@ __attribute((no_stack_protector)) void smcall_handler(uint32_t *args,
reboot_to_bootloader();
} break;
+ case SMCALL_REBOOT_AND_CONNECT: {
+ reboot_and_connect();
+ } break;
+
case SMCALL_REBOOT_AND_UPGRADE: {
const uint8_t *hash = (const uint8_t *)args[0];
reboot_and_upgrade__verified(hash);
### core/embed/sys/smcall/stm32/smcall_numbers.h
@@ -38,6 +38,7 @@ typedef enum {
SMCALL_REBOOT_DEVICE,
SMCALL_REBOOT_TO_BOOTLOADER,
+ SMCALL_REBOOT_AND_CONNECT,
SMCALL_REBOOT_AND_UPGRADE,
SMCALL_REBOOT_TO_OFF,
SMCALL_REBOOT_WITH_RSOD,
### core/embed/sys/smcall/stm32/smcall_stubs.c
@@ -74,6 +74,11 @@ void reboot_to_bootloader(void) {
while (1);
}
+void reboot_and_connect(void) {
+ smcall_invoke0(SMCALL_REBOOT_AND_CONNECT);
+ while (1);
+}
+
void reboot_and_upgrade(const uint8_t hash[32]) {
smcall_invoke1((uint32_t)hash, SMCALL_REBOOT_AND_UPGRADE);
while (1);
### core/embed/sys/startup/inc/sys/bootargs.h
@@ -30,6 +30,8 @@ typedef enum {
BOOT_COMMAND_NONE = 0x00000000,
// Stop and wait for further instructions
BOOT_COMMAND_STOP_AND_WAIT = 0x0FC35A96,
+ // Stop in the bootloader and bring up the host link immediately
+ BOOT_COMMAND_STOP_AND_CONNECT = 0x3B7E1C64,
// Do not ask anything, install an upgrade
BOOT_COMMAND_INSTALL_UPGRADE = 0xFA4A5C8D,
// Show RSOD and wait for user input
### core/embed/sys/startup/inc/sys/bootutils.h
@@ -47,6 +47,16 @@ void __attribute__((noreturn)) reboot_to_off(void);
// halting there and waiting for further user instructions.
void __attribute__((noreturn)) reboot_to_bootloader(void);
+// Resets the device into the bootloader and brings up the host link straight
+// away, skipping the intro screen that reboot_to_bootloader() lands on.
+//
+// Unlike an interaction-less upgrade this carries no consent material and
+// authorizes nothing: it only chooses which screen the bootloader starts on.
+// Use it when a host is already waiting to talk -- notably when the caller has
+// just invalidated its own firmware, where the intro screen would strand the
+// device behind a tap it cannot ask for.
+void __attribute__((noreturn)) reboot_and_connect(void);
+
// Resets the device into the bootloader and automatically continues
// with the installation of new firmware (also known as an
// interaction-less upgrade).
### core/embed/sys/startup/stm32/bootutils.c
@@ -202,6 +202,10 @@ __attribute__((noreturn)) void reboot_to_bootloader(void) {
reboot_with_args(BOOT_COMMAND_STOP_AND_WAIT, NULL, 0);
}
+__attribute__((noreturn)) void reboot_and_connect(void) {
+ reboot_with_args(BOOT_COMMAND_STOP_AND_CONNECT, NULL, 0);
+}
+
__attribute__((noreturn)) void reboot_and_upgrade(const uint8_t hash[32]) {
reboot_with_args(BOOT_COMMAND_INSTALL_UPGRADE, hash, 32);
}
### core/embed/sys/startup/unix/bootutils.c
@@ -70,6 +70,12 @@ __attribute__((noreturn)) void reboot_to_bootloader(void) {
exit(3);
}
+__attribute__((noreturn)) void reboot_and_connect(void) {
+ LOG_WARN("reboot (to bootloader, connect)");
+
+ exit(3);
+}
+
__attribute__((noreturn)) void reboot_and_upgrade(const uint8_t hash[32]) {
LOG_WARN("reboot (upgrade)");
### core/embed/sys/syscall/inc/sys/syscall_numbers.h
@@ -62,6 +62,7 @@ typedef enum {
SYSCALL_REBOOT_DEVICE,
SYSCALL_REBOOT_TO_BOOTLOADER,
+ SYSCALL_REBOOT_AND_CONNECT,
SYSCALL_REBOOT_AND_UPGRADE,
SYSCALL_NOTIFY_SEND,
### core/embed/sys/syscall/stm32/syscall_dispatch.c
@@ -264,6 +264,10 @@ __attribute((no_stack_protector)) void syscall_handler(uint32_t *args,
reboot_to_bootloader();
} break;
+ case SYSCALL_REBOOT_AND_CONNECT: {
+ reboot_and_connect();
+ } break;
+
case SYSCALL_REBOOT_AND_UPGRADE: {
const uint8_t *hash = (const uint8_t *)args[0];
reboot_and_upgrade__verified(hash);
### core/embed/sys/syscall/stm32/syscall_stubs.c
@@ -196,6 +196,11 @@ void reboot_to_bootloader(void) {
while (1);
}
+void reboot_and_connect(void) {
+ syscall_invoke0(SYSCALL_REBOOT_AND_CONNECT);
+ while (1);
+}
+
void reboot_and_upgrade(const uint8_t hash[32]) {
syscall_invoke1((uint32_t)hash, SYSCALL_REBOOT_AND_UPGRADE);
while (1);Why this scored 27/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.