refactor(core): split reboot and reboot_to_bootloader functions
What changed, and why it matters
This commit is a straightforward internal code cleanup: it splits one Python/C function that handled three different reboot behaviors into three separate, clearly named functions. There is no change to what the device actually does during a reboot or firmware upgrade, and no security bug is introduced or fixed.
No security action required; treat as normal refactoring review.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change refactors utils.reboot_to_bootloader(boot_command, boot_args) into three distinct helpers: utils.reboot(), utils.reboot_to_bootloader(), and utils.reboot_and_upgrade(hash). The C MicroPython module, type stubs, homescreen menu, and firmware-upgrade flow are updated to call the appropriate helper. The upgrade path still validates the firmware-header hash length (32 bytes) before passing it to reboot_and_upgrade(). No logic that enforces security checks was added, removed, or weakened.
Changed components
core/embed/sys/startup/unix/bootutils.ccore/embed/upymod/modtrezorutils/modtrezorutils.ccore/mocks/generated/trezorutils.pyicore/src/apps/homescreen/device_menu.pycore/src/apps/management/reboot_to_bootloader.pycore/src/trezor/utils.pyInspect captured patch +81 / −59
diff --git a/core/embed/sys/startup/unix/bootutils.c b/core/embed/sys/startup/unix/bootutils.c
index 54e8ed21..0fdb2c91 100644
--- a/core/embed/sys/startup/unix/bootutils.c
+++ b/core/embed/sys/startup/unix/bootutils.c
@@ -67,6 +67,12 @@ __attribute__((noreturn)) void reboot_to_bootloader(void) {
exit(3);
}
+__attribute__((noreturn)) void reboot_and_upgrade(const uint8_t hash[32]) {
+ printf("reboot (upgrade)\n");
+
+ exit(3);
+}
+
__attribute__((noreturn)) void reboot_to_off(void) {
printf("reboot (power off)\n");
diff --git a/core/embed/upymod/modtrezorutils/modtrezorutils.c b/core/embed/upymod/modtrezorutils/modtrezorutils.c
index 0e27a25c..f68cfb35 100644
--- a/core/embed/upymod/modtrezorutils/modtrezorutils.c
+++ b/core/embed/upymod/modtrezorutils/modtrezorutils.c
@@ -481,54 +481,56 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorutils_check_heap_fragmentation_obj,
mod_trezorutils_check_heap_fragmentation);
#endif // !PYOPT
-/// def reboot_to_bootloader(
-/// boot_command : int = 0,
-/// boot_args : AnyBytes | None = None,
+/// def reboot_and_upgrade(
+/// hash : AnyBytes,
/// ) -> None:
/// """
-/// Reboots to bootloader.
+/// Reboots to perform upgrade to FW with specified hash.
/// """
-STATIC mp_obj_t mod_trezorutils_reboot_to_bootloader(size_t n_args,
- const mp_obj_t *args) {
-#ifndef TREZOR_EMULATOR
- if (n_args > 0 && args[0] != mp_const_none) {
- mp_int_t value = mp_obj_get_int(args[0]);
-
- switch (value) {
- case 0:
- // Reboot and stay in bootloader
- reboot_to_bootloader();
- break;
- case 1:
- // Reboot and continue with the firmware upgrade
- mp_buffer_info_t hash = {0};
-
- if (n_args > 1 && args[1] != mp_const_none) {
- mp_get_buffer_raise(args[1], &hash, MP_BUFFER_READ);
- }
-
- if (hash.len != 32) {
- mp_raise_ValueError(MP_ERROR_TEXT("Invalid value."));
- }
-
- reboot_and_upgrade((uint8_t *)hash.buf);
- break;
- default:
- mp_raise_ValueError(MP_ERROR_TEXT("Invalid value."));
- break;
- }
- } else {
- // Just reboot and go through the normal boot sequence
- reboot_device();
+STATIC mp_obj_t mod_trezorutils_reboot_and_upgrade(mp_obj_t hash_obj) {
+ // Reboot and continue with the firmware upgrade
+ mp_buffer_info_t hash = {0};
+
+ mp_get_buffer_raise(hash_obj, &hash, MP_BUFFER_READ);
+
+ if (hash.len != 32) {
+ mp_raise_ValueError(MP_ERROR_TEXT("Invalid value."));
}
-#endif
+ reboot_and_upgrade((uint8_t *)hash.buf);
+ return mp_const_none;
+}
+
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorutils_reboot_and_upgrade_obj,
+ mod_trezorutils_reboot_and_upgrade);
+
+/// def reboot_to_bootloader() -> None:
+/// """
+/// Reboots the device and stay in bootloader.
+/// """
+STATIC mp_obj_t mod_trezorutils_reboot_to_bootloader(void) {
+ // Reboot and stay in bootloader
+ reboot_to_bootloader();
+
+ return mp_const_none;
+}
+
+STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorutils_reboot_to_bootloader_obj,
+ mod_trezorutils_reboot_to_bootloader);
+
+/// def reboot() -> None:
+/// """
+/// Reboots the device.
+/// """
+STATIC mp_obj_t mod_trezorutils_reboot(void) {
+ // Just reboot and go through the normal boot sequence
+ reboot_device();
+
return mp_const_none;
}
-STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(
- mod_trezorutils_reboot_to_bootloader_obj, 0, 2,
- mod_trezorutils_reboot_to_bootloader);
+STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorutils_reboot_obj,
+ mod_trezorutils_reboot);
/// VersionTuple = Tuple[int, int, int, int]
@@ -741,8 +743,11 @@ STATIC const mp_rom_map_elem_t mp_module_trezorutils_globals_table[] = {
MP_ROM_PTR(&mod_trezorutils_firmware_hash_obj)},
{MP_ROM_QSTR(MP_QSTR_firmware_vendor),
MP_ROM_PTR(&mod_trezorutils_firmware_vendor_obj)},
+ {MP_ROM_QSTR(MP_QSTR_reboot_and_upgrade),
+ MP_ROM_PTR(&mod_trezorutils_reboot_and_upgrade_obj)},
{MP_ROM_QSTR(MP_QSTR_reboot_to_bootloader),
MP_ROM_PTR(&mod_trezorutils_reboot_to_bootloader_obj)},
+ {MP_ROM_QSTR(MP_QSTR_reboot), MP_ROM_PTR(&mod_trezorutils_reboot_obj)},
{MP_ROM_QSTR(MP_QSTR_check_firmware_header),
MP_ROM_PTR(&mod_trezorutils_check_firmware_header_obj)},
{MP_ROM_QSTR(MP_QSTR_bootloader_locked),
diff --git a/core/mocks/generated/trezorutils.pyi b/core/mocks/generated/trezorutils.pyi
index c471dfad..12f33f27 100644
--- a/core/mocks/generated/trezorutils.pyi
+++ b/core/mocks/generated/trezorutils.pyi
@@ -157,12 +157,25 @@ if __debug__:
# upymod/modtrezorutils/modtrezorutils.c
-def reboot_to_bootloader(
- boot_command : int = 0,
- boot_args : AnyBytes | None = None,
+def reboot_and_upgrade(
+ hash : AnyBytes,
) -> None:
"""
- Reboots to bootloader.
+ Reboots to perform upgrade to FW with specified hash.
+ """
+
+
+# upymod/modtrezorutils/modtrezorutils.c
+def reboot_to_bootloader() -> None:
+ """
+ Reboots the device and stay in bootloader.
+ """
+
+
+# upymod/modtrezorutils/modtrezorutils.c
+def reboot() -> None:
+ """
+ Reboots the device.
"""
VersionTuple = Tuple[int, int, int, int]
diff --git a/core/src/apps/homescreen/device_menu.py b/core/src/apps/homescreen/device_menu.py
index 0ed80810..2af4ccb3 100644
--- a/core/src/apps/homescreen/device_menu.py
+++ b/core/src/apps/homescreen/device_menu.py
@@ -442,16 +442,14 @@ async def handle_device_menu() -> None:
io.pm.hibernate()
raise RuntimeError
elif menu_result is DeviceMenuResult.Reboot:
- from trezor.utils import reboot_to_bootloader
+ from trezor.utils import reboot
- # Empty boot command results to a normal reboot
- reboot_to_bootloader()
+ reboot()
raise RuntimeError
elif menu_result is DeviceMenuResult.RebootToBootloader:
- from trezor.enums import BootCommand
from trezor.utils import reboot_to_bootloader
- reboot_to_bootloader(BootCommand.STOP_AND_WAIT)
+ reboot_to_bootloader()
raise RuntimeError
elif menu_result is CANCELLED:
return
diff --git a/core/src/apps/management/reboot_to_bootloader.py b/core/src/apps/management/reboot_to_bootloader.py
index 8d12b31f..659c841d 100644
--- a/core/src/apps/management/reboot_to_bootloader.py
+++ b/core/src/apps/management/reboot_to_bootloader.py
@@ -6,7 +6,6 @@ if TYPE_CHECKING:
from buffer_types import AnyBytes
from typing import NoReturn
- from trezor.enums import BootCommand
from trezor.messages import RebootToBootloader
@@ -15,11 +14,10 @@ _REBOOT_SUCCESS_TIMEOUT_MS = const(500)
async def install_upgrade(
firmware_header: AnyBytes, language_data_length: int
-) -> tuple[BootCommand, AnyBytes]:
+) -> AnyBytes:
from ubinascii import hexlify
from trezor import TR, utils, wire
- from trezor.enums import BootCommand
from trezor.ui.layouts import confirm_firmware_update, show_wait_text
from apps.management.change_language import do_change_language
@@ -61,7 +59,7 @@ async def install_upgrade(
# Continue firmware upgrade even if language change failed
pass
- return BootCommand.INSTALL_UPGRADE, hdr.hash
+ return hdr.hash
async def reboot_to_bootloader(msg: RebootToBootloader) -> NoReturn:
@@ -83,9 +81,7 @@ async def reboot_to_bootloader(msg: RebootToBootloader) -> NoReturn:
and msg.firmware_header is not None
and is_official
):
- boot_command, boot_args = await install_upgrade(
- msg.firmware_header, msg.language_data_length
- )
+ fw_hash = await install_upgrade(msg.firmware_header, msg.language_data_length)
else:
await confirm_action(
@@ -95,8 +91,7 @@ async def reboot_to_bootloader(msg: RebootToBootloader) -> NoReturn:
verb=TR.buttons__restart,
prompt_screen=True,
)
- boot_command = BootCommand.STOP_AND_WAIT
- boot_args = None
+ fw_hash = None
ctx = get_context()
# After ACK-ing the `Success` message (over THP), the host may already be waiting for the bootloader to start.
@@ -110,5 +105,8 @@ async def reboot_to_bootloader(msg: RebootToBootloader) -> NoReturn:
utime.sleep_ms(10)
# reboot to the bootloader, pass the firmware header hash if any
- utils.reboot_to_bootloader(boot_command, boot_args)
+ if fw_hash is not None:
+ utils.reboot_and_upgrade(fw_hash)
+ else:
+ utils.reboot_to_bootloader()
raise RuntimeError
diff --git a/core/src/trezor/utils.py b/core/src/trezor/utils.py
index 7b92b3c8..bce74d97 100644
--- a/core/src/trezor/utils.py
+++ b/core/src/trezor/utils.py
@@ -45,6 +45,8 @@ from trezorutils import ( # noqa: F401
memzero,
notify_send,
presize_module,
+ reboot,
+ reboot_and_upgrade,
reboot_to_bootloader,
sd_hotswap_enabled,
unit_btconly,
Why this scored 15/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.