diff --git a/core/.changelog.d/2284.added b/core/.changelog.d/2284.added new file mode 100644 index 000000000..d6fbe284e --- /dev/null +++ b/core/.changelog.d/2284.added @@ -0,0 +1 @@ +Jump and stay in bootloader from firmware through SVC call reverse trampoline. diff --git a/core/embed/bootloader/.changelog.d/2284.added b/core/embed/bootloader/.changelog.d/2284.added new file mode 100644 index 000000000..d6fbe284e --- /dev/null +++ b/core/embed/bootloader/.changelog.d/2284.added @@ -0,0 +1 @@ +Jump and stay in bootloader from firmware through SVC call reverse trampoline. diff --git a/core/embed/bootloader/main.c b/core/embed/bootloader/main.c index 8b0f0994a..d59dfaf09 100644 --- a/core/embed/bootloader/main.c +++ b/core/embed/bootloader/main.c @@ -242,6 +242,10 @@ static void check_bootloader_version(void) { #endif int main(void) { + // grab "stay in bootloader" flag as soon as possible + register uint32_t r11 __asm__("r11"); + volatile uint32_t stay_in_bootloader_flag = r11; + random_delays_init(); // display_init_seq(); #if defined TREZOR_MODEL_T @@ -281,6 +285,9 @@ int main(void) { vendor_header vhdr; image_header hdr; secbool stay_in_bootloader = secfalse; // flag to stay in bootloader + if (stay_in_bootloader_flag == STAY_IN_BOOTLOADER_FLAG) { + stay_in_bootloader = sectrue; + } // detect whether the devices contains a valid firmware diff --git a/core/embed/extmod/modtrezorutils/modtrezorutils.c b/core/embed/extmod/modtrezorutils/modtrezorutils.c index 41449742c..0768ccf11 100644 --- a/core/embed/extmod/modtrezorutils/modtrezorutils.c +++ b/core/embed/extmod/modtrezorutils/modtrezorutils.c @@ -19,6 +19,9 @@ #include "py/objstr.h" #include "py/runtime.h" +#ifndef TREZOR_EMULATOR +#include "supervise.h" +#endif #include "version.h" @@ -245,6 +248,19 @@ STATIC mp_obj_t mod_trezorutils_get_firmware_chunk(const mp_obj_t index_obj, STATIC MP_DEFINE_CONST_FUN_OBJ_3(mod_trezorutils_get_firmware_chunk_obj, mod_trezorutils_get_firmware_chunk); +/// def reboot_to_bootloader() -> None: +/// """ +/// Reboots to bootloader. +/// """ +STATIC mp_obj_t mod_trezorutils_reboot_to_bootloader() { +#ifndef TREZOR_EMULATOR + svc_reboot_to_bootloader(); +#endif + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorutils_reboot_to_bootloader_obj, + mod_trezorutils_reboot_to_bootloader); + STATIC mp_obj_str_t mod_trezorutils_revision_obj = { {&mp_type_bytes}, 0, sizeof(SCM_REVISION) - 1, (const byte *)SCM_REVISION}; @@ -272,7 +288,8 @@ STATIC const mp_rom_map_elem_t mp_module_trezorutils_globals_table[] = { MP_ROM_PTR(&mod_trezorutils_firmware_sector_size_obj)}, {MP_ROM_QSTR(MP_QSTR_FIRMWARE_SECTORS_COUNT), MP_ROM_INT(FIRMWARE_SECTORS_COUNT)}, - + {MP_ROM_QSTR(MP_QSTR_reboot_to_bootloader), + MP_ROM_PTR(&mod_trezorutils_reboot_to_bootloader_obj)}, // various built-in constants {MP_ROM_QSTR(MP_QSTR_SCM_REVISION), MP_ROM_PTR(&mod_trezorutils_revision_obj)}, diff --git a/core/embed/firmware/main.c b/core/embed/firmware/main.c index 2146f4097..66be5c6af 100644 --- a/core/embed/firmware/main.c +++ b/core/embed/firmware/main.c @@ -41,6 +41,7 @@ #include "compiler_traits.h" #include "display.h" #include "flash.h" +#include "image.h" #include "mpu.h" #include "random_delays.h" #ifdef SYSTEM_VIEW @@ -180,6 +181,13 @@ void UsageFault_Handler(void) { error_shutdown("Internal error", "(UF)", NULL, NULL); } +__attribute__((noreturn)) void reboot_to_bootloader() { + jump_to_with_flag(BOOTLOADER_START + IMAGE_HEADER_SIZE, + STAY_IN_BOOTLOADER_FLAG); + for (;;) + ; +} + void SVC_C_Handler(uint32_t *stack) { uint8_t svc_number = ((uint8_t *)stack[6])[-2]; switch (svc_number) { @@ -202,6 +210,16 @@ void SVC_C_Handler(uint32_t *stack) { for (;;) ; break; + case SVC_REBOOT_TO_BOOTLOADER: + mpu_config_bootloader(); + __asm__ volatile("msr control, %0" ::"r"(0x0)); + __asm__ volatile("isb"); + // See stack layout in + // https://developer.arm.com/documentation/ka004005/latest We are changing + // return address in PC to land into reboot to avoid any bug with ROP and + // raising privileges. + stack[6] = (uintptr_t)reboot_to_bootloader; + return; default: stack[0] = 0xffffffff; break; diff --git a/core/embed/trezorhal/common.h b/core/embed/trezorhal/common.h index 90c4e20f1..57ab303c6 100644 --- a/core/embed/trezorhal/common.h +++ b/core/embed/trezorhal/common.h @@ -49,6 +49,8 @@ }) #endif +#define STAY_IN_BOOTLOADER_FLAG 0x0FC35A96 + void shutdown(void); void __attribute__((noreturn)) @@ -78,5 +80,6 @@ extern uint8_t HW_ENTROPY_DATA[HW_ENTROPY_LEN]; void memset_reg(volatile void *start, volatile void *stop, uint32_t val); void jump_to(uint32_t address); void jump_to_unprivileged(uint32_t address); +void jump_to_with_flag(uint32_t address, uint32_t register_flag); #endif diff --git a/core/embed/trezorhal/supervise.h b/core/embed/trezorhal/supervise.h index 9fc5fba8c..851c61aaa 100644 --- a/core/embed/trezorhal/supervise.h +++ b/core/embed/trezorhal/supervise.h @@ -4,9 +4,11 @@ #define SVC_DISABLE_IRQ 1 #define SVC_SET_PRIORITY 2 #define SVC_SHUTDOWN 4 +#define SVC_REBOOT_TO_BOOTLOADER 5 // from util.s extern void shutdown_privileged(void); +extern void reboot_to_bootloader(void); static inline uint32_t is_mode_unprivileged(void) { uint32_t r0; @@ -50,3 +52,10 @@ static inline void svc_shutdown(void) { shutdown_privileged(); } } +static inline void svc_reboot_to_bootloader(void) { + if (is_mode_unprivileged()) { + __asm__ __volatile__("svc %0" ::"i"(SVC_REBOOT_TO_BOOTLOADER) : "memory"); + } else { + reboot_to_bootloader(); + } +} diff --git a/core/embed/trezorhal/util.s b/core/embed/trezorhal/util.s index a2ea44793..74b0ca4c0 100644 --- a/core/embed/trezorhal/util.s +++ b/core/embed/trezorhal/util.s @@ -16,10 +16,22 @@ memset_reg: bne .L_loop_begin bx lr + // Jump to address given in first argument R0 that points to next's stage's VTOR + // Clear memory and all registers before jump .global jump_to .type jump_to, STT_FUNC jump_to: + ldr r1, =0 + bl jump_to_with_flag + + // Jump to address given in first argument R0 that points to next's stage's VTOR + // Clear memory and all registers before jump. Second argument R1 is copied to R11 + // and kept after jump. + .global jump_to_with_flag + .type jump_to_with_flag, STT_FUNC +jump_to_with_flag: mov r4, r0 // save input argument r0 (the address of the next stage's vector table) (r4 is callee save) + mov r11, r1 // save second argument in "flag" register because we'll be cleaning RAM // this subroutine re-points the exception handlers before the C code // that comprises them has been given a good environment to run. // therefore, this code needs to disable interrupts before the VTOR @@ -39,7 +51,7 @@ jump_to: ldr r2, =0 // r2 - the word-sized value to be written bl memset_reg mov lr, r4 - // clear out the general purpose registers before the next stage's code can run (even the NMI exception handler) + // clear out the general purpose registers before the next stage's except the register with flag R11 ldr r0, =0 mov r1, r0 mov r2, r0 @@ -51,7 +63,6 @@ jump_to: mov r8, r0 mov r9, r0 mov r10, r0 - mov r11, r0 mov r12, r0 // give the next stage a fresh main stack pointer ldr r0, [lr] // set r0 to the main stack pointer in the next stage's vector table diff --git a/core/mocks/generated/trezorutils.pyi b/core/mocks/generated/trezorutils.pyi index b9252708f..50396a0b0 100644 --- a/core/mocks/generated/trezorutils.pyi +++ b/core/mocks/generated/trezorutils.pyi @@ -72,6 +72,13 @@ def get_firmware_chunk(index: int, offset: int, buffer: bytearray) -> None: """ Reads a chunk of the firmware into `buffer`. """ + + +# extmod/modtrezorutils/modtrezorutils.c +def reboot_to_bootloader() -> None: + """ + Reboots to bootloader. + """ SCM_REVISION: bytes VERSION_MAJOR: int VERSION_MINOR: int diff --git a/core/src/all_modules.py b/core/src/all_modules.py index b28dd14ad..52f5d3a6a 100644 --- a/core/src/all_modules.py +++ b/core/src/all_modules.py @@ -371,6 +371,8 @@ apps.management.get_next_u2f_counter import apps.management.get_next_u2f_counter apps.management.get_nonce import apps.management.get_nonce +apps.management.reboot_to_bootloader +import apps.management.reboot_to_bootloader apps.management.recovery_device import apps.management.recovery_device apps.management.recovery_device.homescreen diff --git a/core/src/apps/management/reboot_to_bootloader.py b/core/src/apps/management/reboot_to_bootloader.py new file mode 100644 index 000000000..edd3ca744 --- /dev/null +++ b/core/src/apps/management/reboot_to_bootloader.py @@ -0,0 +1,17 @@ +from typing import TYPE_CHECKING + +from trezor import utils, wire +from trezor.messages import RebootToBootloader, Success + +if TYPE_CHECKING: + from typing import NoReturn + + pass + + +async def reboot_to_bootloader(ctx: wire.Context, msg: RebootToBootloader) -> NoReturn: + await ctx.write(Success()) + # writing synchronously twice makes USB flush properly before reboot + await ctx.write(Success()) + utils.reboot_to_bootloader() + raise RuntimeError diff --git a/core/src/apps/workflow_handlers.py b/core/src/apps/workflow_handlers.py index a5839084e..22aa4a139 100644 --- a/core/src/apps/workflow_handlers.py +++ b/core/src/apps/workflow_handlers.py @@ -49,6 +49,8 @@ def find_message_handler_module(msg_type: int) -> str: return "apps.management.change_wipe_code" elif msg_type == MessageType.GetNonce: return "apps.management.get_nonce" + elif msg_type == MessageType.RebootToBootloader: + return "apps.management.reboot_to_bootloader" if utils.MODEL in ("T",) and msg_type == MessageType.SdProtect: return "apps.management.sd_protect" diff --git a/core/src/trezor/utils.py b/core/src/trezor/utils.py index 0263fa3ed..ecd67208a 100644 --- a/core/src/trezor/utils.py +++ b/core/src/trezor/utils.py @@ -16,6 +16,7 @@ from trezorutils import ( # noqa: F401 get_firmware_chunk, halt, memcpy, + reboot_to_bootloader, ) from typing import TYPE_CHECKING diff --git a/python/.changelog.d/2284.added b/python/.changelog.d/2284.added new file mode 100644 index 000000000..d6fbe284e --- /dev/null +++ b/python/.changelog.d/2284.added @@ -0,0 +1 @@ +Jump and stay in bootloader from firmware through SVC call reverse trampoline. diff --git a/python/src/trezorlib/cli/device.py b/python/src/trezorlib/cli/device.py index 402fb784c..320b91c6c 100644 --- a/python/src/trezorlib/cli/device.py +++ b/python/src/trezorlib/cli/device.py @@ -278,8 +278,4 @@ def reboot_to_bootloader(obj: "TrezorConnection") -> str: # avoid using @with_client because it closes the session afterwards, # which triggers double prompt on device with obj.client_context() as client: - if client.features.model != "1": - click.echo( - f"Warning: Rebooting into bootloader not supported on Trezor {client.features.model}" - ) return device.reboot_to_bootloader(client)