From 828ba7b5b01f9997a2f8e63e2e5f19d4a3f607af Mon Sep 17 00:00:00 2001 From: Pavol Rusnak Date: Thu, 21 Feb 2019 09:52:28 +0100 Subject: [PATCH 01/15] bootloader: enable MPU, introduce delays to USB stack --- SConscript.bootloader | 1 + embed/bootloader/main.c | 6 +++- embed/firmware/main.c | 2 +- embed/trezorhal/common.c | 21 ++++++++++++- embed/trezorhal/common.h | 9 ++++-- embed/trezorhal/mpu.c | 55 ++++++++++++++++++++++++++++++++-- embed/trezorhal/mpu.h | 4 ++- embed/trezorhal/stm32.c | 3 +- embed/trezorhal/usb.c | 10 +++++-- embed/trezorhal/usb_hid-impl.h | 5 ++-- embed/trezorhal/usb_vcp-impl.h | 6 ++-- embed/trezorhal/usbd_def.h | 5 +++- embed/unix/common.h | 4 +-- 13 files changed, 110 insertions(+), 21 deletions(-) diff --git a/SConscript.bootloader b/SConscript.bootloader index 1d79537653..ec0ba8462c 100644 --- a/SConscript.bootloader +++ b/SConscript.bootloader @@ -83,6 +83,7 @@ SOURCE_TREZORHAL = [ 'embed/trezorhal/image.c', 'embed/trezorhal/flash.c', 'embed/trezorhal/mini_printf.c', + 'embed/trezorhal/mpu.c', 'embed/trezorhal/rng.c', 'embed/trezorhal/stm32.c', 'embed/trezorhal/touch.c', diff --git a/embed/bootloader/main.c b/embed/bootloader/main.c index 61fa8df1ca..f48d72cdda 100644 --- a/embed/bootloader/main.c +++ b/embed/bootloader/main.c @@ -21,6 +21,7 @@ #include #include "common.h" +#include "mpu.h" #include "image.h" #include "flash.h" #include "display.h" @@ -224,6 +225,8 @@ static void check_bootloader_version(void) int main(void) { + mpu_config_bootloader(); + main_start: display_clear(); @@ -379,9 +382,10 @@ main_start: ui_fadeout(); } - // mpu_config(); + // mpu_config_firmware(); // jump_to_unprivileged(FIRMWARE_START + vhdr.hdrlen + IMAGE_HEADER_SIZE); + mpu_config_off(); jump_to(FIRMWARE_START + vhdr.hdrlen + IMAGE_HEADER_SIZE); return 0; diff --git a/embed/firmware/main.c b/embed/firmware/main.c index 5d517fcc01..e0b5f2cafd 100644 --- a/embed/firmware/main.c +++ b/embed/firmware/main.c @@ -54,7 +54,7 @@ int main(void) #if TREZOR_MODEL == T check_and_replace_bootloader(); // Enable MPU - mpu_config(); + mpu_config_firmware(); #endif // Init peripherals diff --git a/embed/trezorhal/common.c b/embed/trezorhal/common.c index c3afd92619..d0dc28f8d6 100644 --- a/embed/trezorhal/common.c +++ b/embed/trezorhal/common.c @@ -29,7 +29,8 @@ #include "stm32f4xx_ll_utils.h" -void shutdown(void); +// from util.s +extern void shutdown(void); #define COLOR_FATAL_ERROR RGB16(0x7F, 0x00, 0x00) @@ -115,6 +116,24 @@ void hal_delay(uint32_t ms) HAL_Delay(ms); } +void delay_random(void) +{ + int wait = rng_get() & 0xff; + volatile int i = 0; + volatile int j = wait; + while (i < wait) { + if (i + j != wait) { + shutdown(); + } + ++i; + --j; + } + // Double-check loop completion. + if (i != wait || j != 0) { + shutdown(); + } +} + // reference RM0090 section 35.12.1 Figure 413 #define USB_OTG_HS_DATA_FIFO_RAM (USB_OTG_HS_PERIPH_BASE + 0x20000U) #define USB_OTG_HS_DATA_FIFO_SIZE (4096U) diff --git a/embed/trezorhal/common.h b/embed/trezorhal/common.h index f886739e0e..3fac1dec62 100644 --- a/embed/trezorhal/common.h +++ b/embed/trezorhal/common.h @@ -26,11 +26,14 @@ #define XSTR(s) STR(s) #define STR(s) #s +#ifndef MIN_8bits +#define MIN_8bits(a, b) ({ typeof(a) _a = (a); typeof(b) _b = (b); _a < _b ? (_a & 0xFF) : (_b & 0xFF); }) +#endif #ifndef MIN -#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MIN(a, b) ({ typeof(a) _a = (a); typeof(b) _b = (b); _a < _b ? _a : _b; }) #endif #ifndef MAX -#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define MAX(a, b) ({ typeof(a) _a = (a); typeof(b) _b = (b); _a > _b ? _a : _b; }) #endif void __attribute__((noreturn)) __fatal_error(const char *expr, const char *msg, const char *file, int line, const char *func); @@ -40,6 +43,8 @@ void __attribute__((noreturn)) error_shutdown(const char *line1, const char *lin void hal_delay(uint32_t ms); +void delay_random(void); + void clear_otg_hs_memory(void); extern uint32_t __stack_chk_guard; diff --git a/embed/trezorhal/mpu.c b/embed/trezorhal/mpu.c index 17fc68cc2d..05e0453350 100644 --- a/embed/trezorhal/mpu.c +++ b/embed/trezorhal/mpu.c @@ -27,10 +27,61 @@ #define MPU_SUBREGION_DISABLE(X) ((X) << MPU_RASR_SRD_Pos) -void mpu_config(void) +void mpu_config_off(void) { // Disable MPU HAL_MPU_Disable(); +} + +void mpu_config_bootloader(void) +{ + // Disable MPU + HAL_MPU_Disable(); + + // Note: later entries overwrite previous ones + + // Flash (0x08000000 - 0x081FFFFF, 2 MiB, read-write) + MPU->RBAR = FLASH_BASE | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER0; + MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_FLASH | LL_MPU_REGION_SIZE_2MB | LL_MPU_REGION_PRIV_RO_URO; + + // Flash (0x0800C000 - 0x0800FFFF, 16 KiB, no access) + MPU->RBAR = FLASH_BASE | 0xC000 | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER1; + MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_FLASH | LL_MPU_REGION_SIZE_16KB | LL_MPU_REGION_NO_ACCESS; + + // Flash (0x0810C000 - 0x0810FFFF, 16 KiB, no access) + MPU->RBAR = FLASH_BASE | 0x10C000 | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER2; + MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_FLASH | LL_MPU_REGION_SIZE_16KB | LL_MPU_REGION_NO_ACCESS; + + // SRAM (0x20000000 - 0x2002FFFF, 192 KiB = 256 KiB except 2/8 at end, read-write, execute never) + MPU->RBAR = SRAM_BASE | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER3; + MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_SRAM | LL_MPU_REGION_SIZE_256KB | LL_MPU_REGION_FULL_ACCESS | MPU_RASR_XN_Msk | MPU_SUBREGION_DISABLE(0xC0); + + // Peripherals (0x40000000 - 0x5FFFFFFF, read-write, execute never) + // External RAM (0x60000000 - 0x7FFFFFFF, read-write, execute never) + MPU->RBAR = PERIPH_BASE | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER4; + MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_PERIPH | LL_MPU_REGION_SIZE_1GB | LL_MPU_REGION_FULL_ACCESS | MPU_RASR_XN_Msk; + +#ifdef STM32F427xx + // CCMRAM (0x10000000 - 0x1000FFFF, read-write, execute never) + MPU->RBAR = CCMDATARAM_BASE | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER5; + MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_SRAM | LL_MPU_REGION_SIZE_64KB | LL_MPU_REGION_FULL_ACCESS | MPU_RASR_XN_Msk; +#elif STM32F405xx + // no CCMRAM +#else +#error Unsupported MCU +#endif + + // Enable MPU + HAL_MPU_Enable(LL_MPU_CTRL_HARDFAULT_NMI); +} + + +void mpu_config_firmware(void) +{ + // Disable MPU + HAL_MPU_Disable(); + + // Note: later entries overwrite previous ones /* // Boardloader (0x08000000 - 0x0800FFFF, 64 KiB, read-only, execute never) @@ -77,5 +128,5 @@ void mpu_config(void) #endif // Enable MPU - HAL_MPU_Enable(0); + HAL_MPU_Enable(LL_MPU_CTRL_HARDFAULT_NMI); } diff --git a/embed/trezorhal/mpu.h b/embed/trezorhal/mpu.h index 2a7ccf6fcd..c7c136849e 100644 --- a/embed/trezorhal/mpu.h +++ b/embed/trezorhal/mpu.h @@ -20,6 +20,8 @@ #ifndef __MPU_H__ #define __MPU_H__ -void mpu_config(void); +void mpu_config_off(void); +void mpu_config_bootloader(void); +void mpu_config_firmware(void); #endif diff --git a/embed/trezorhal/stm32.c b/embed/trezorhal/stm32.c index 41de13aafb..5c74fd350a 100644 --- a/embed/trezorhal/stm32.c +++ b/embed/trezorhal/stm32.c @@ -79,7 +79,8 @@ void SysTick_Handler(void) uwTick++; } -void shutdown(void); +// from util.s +extern void shutdown(void); void PVD_IRQHandler(void) { diff --git a/embed/trezorhal/usb.c b/embed/trezorhal/usb.c index 57dff2b0f5..03ac2cd5b5 100644 --- a/embed/trezorhal/usb.c +++ b/embed/trezorhal/usb.c @@ -314,6 +314,7 @@ static uint8_t usb_class_deinit(USBD_HandleTypeDef *dev, uint8_t cfg_idx) { #define USB_WEBUSB_URL_SCHEME_HTTPS 1 static uint8_t usb_class_setup(USBD_HandleTypeDef *dev, USBD_SetupReqTypedef *req) { + delay_random(); if (((req->bmRequest & USB_REQ_TYPE_MASK) != USB_REQ_TYPE_CLASS) && ((req->bmRequest & USB_REQ_TYPE_MASK) != USB_REQ_TYPE_STANDARD) && ((req->bmRequest & USB_REQ_TYPE_MASK) != USB_REQ_TYPE_VENDOR)) { @@ -330,7 +331,7 @@ static uint8_t usb_class_setup(USBD_HandleTypeDef *dev, USBD_SetupReqTypedef *re USB_WEBUSB_URL_SCHEME_HTTPS, // uint8_t bScheme 't', 'r', 'e', 'z', 'o', 'r', '.', 'i', 'o', '/', 's', 't', 'a', 'r', 't', // char URL[] }; - USBD_CtlSendData(dev, UNCONST(webusb_url), MIN(req->wLength, sizeof(webusb_url))); + USBD_CtlSendData(dev, UNCONST(webusb_url), MIN_8bits(req->wLength, sizeof(webusb_url))); return USBD_OK; } else { USBD_CtlError(dev, req); @@ -354,7 +355,7 @@ static uint8_t usb_class_setup(USBD_HandleTypeDef *dev, USBD_SetupReqTypedef *re 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // subCompatibleId 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved }; - USBD_CtlSendData(dev, UNCONST(winusb_wcid), MIN(req->wLength, sizeof(winusb_wcid))); + USBD_CtlSendData(dev, UNCONST(winusb_wcid), MIN_8bits(req->wLength, sizeof(winusb_wcid))); return USBD_OK; } else { USBD_CtlError(dev, req); @@ -380,7 +381,7 @@ static uint8_t usb_class_setup(USBD_HandleTypeDef *dev, USBD_SetupReqTypedef *re 0x50, 0x00, 0x00, 0x00, // dwPropertyDataLength '{', 0x00, 'c', 0x00, '6', 0x00, 'c', 0x00, '3', 0x00, '7', 0x00, '4', 0x00, 'a', 0x00, '6', 0x00, '-', 0x00, '2', 0x00, '2', 0x00, '8', 0x00, '5', 0x00, '-', 0x00, '4', 0x00, 'c', 0x00, 'b', 0x00, '8', 0x00, '-', 0x00, 'a', 0x00, 'b', 0x00, '4', 0x00, '3', 0x00, '-', 0x00, '1', 0x00, '7', 0x00, '6', 0x00, '4', 0x00, '7', 0x00, 'c', 0x00, 'e', 0x00, 'a', 0x00, '5', 0x00, '0', 0x00, '3', 0x00, 'd', 0x00, '}', 0x00, 0x00, 0x00, 0x00, 0x00, // propertyData }; - USBD_CtlSendData(dev, UNCONST(winusb_guid), MIN(req->wLength, sizeof(winusb_guid))); + USBD_CtlSendData(dev, UNCONST(winusb_guid), MIN_8bits(req->wLength, sizeof(winusb_guid))); return USBD_OK; } else { USBD_CtlError(dev, req); @@ -410,6 +411,7 @@ static uint8_t usb_class_setup(USBD_HandleTypeDef *dev, USBD_SetupReqTypedef *re } static uint8_t usb_class_data_in(USBD_HandleTypeDef *dev, uint8_t ep_num) { + delay_random(); for (int i = 0; i < USBD_MAX_NUM_INTERFACES; i++) { switch (usb_ifaces[i].type) { case USB_IFACE_TYPE_HID: @@ -429,6 +431,7 @@ static uint8_t usb_class_data_in(USBD_HandleTypeDef *dev, uint8_t ep_num) { } static uint8_t usb_class_data_out(USBD_HandleTypeDef *dev, uint8_t ep_num) { + delay_random(); for (int i = 0; i < USBD_MAX_NUM_INTERFACES; i++) { switch (usb_ifaces[i].type) { case USB_IFACE_TYPE_HID: @@ -448,6 +451,7 @@ static uint8_t usb_class_data_out(USBD_HandleTypeDef *dev, uint8_t ep_num) { } static uint8_t usb_class_sof(USBD_HandleTypeDef *dev) { + delay_random(); for (int i = 0; i < USBD_MAX_NUM_INTERFACES; i++) { switch (usb_ifaces[i].type) { case USB_IFACE_TYPE_VCP: diff --git a/embed/trezorhal/usb_hid-impl.h b/embed/trezorhal/usb_hid-impl.h index 084342557e..7b20c9beeb 100644 --- a/embed/trezorhal/usb_hid-impl.h +++ b/embed/trezorhal/usb_hid-impl.h @@ -254,7 +254,6 @@ static void usb_hid_class_deinit(USBD_HandleTypeDef *dev, usb_hid_state_t *state } static int usb_hid_class_setup(USBD_HandleTypeDef *dev, usb_hid_state_t *state, USBD_SetupReqTypedef *req) { - switch (req->bmRequest & USB_REQ_TYPE_MASK) { // Class request @@ -302,11 +301,11 @@ static int usb_hid_class_setup(USBD_HandleTypeDef *dev, usb_hid_state_t *state, switch (req->wValue >> 8) { case USB_DESC_TYPE_HID: - USBD_CtlSendData(dev, UNCONST(&state->desc_block->hid), MIN(req->wLength, sizeof(state->desc_block->hid))); + USBD_CtlSendData(dev, UNCONST(&state->desc_block->hid), MIN_8bits(req->wLength, sizeof(state->desc_block->hid))); return USBD_OK; case USB_DESC_TYPE_REPORT: - USBD_CtlSendData(dev, UNCONST(state->report_desc), MIN(req->wLength, state->report_desc_len)); + USBD_CtlSendData(dev, UNCONST(state->report_desc), MIN_8bits(req->wLength, state->report_desc_len)); return USBD_OK; default: diff --git a/embed/trezorhal/usb_vcp-impl.h b/embed/trezorhal/usb_vcp-impl.h index 28aa63875d..9f8d81e2a8 100644 --- a/embed/trezorhal/usb_vcp-impl.h +++ b/embed/trezorhal/usb_vcp-impl.h @@ -364,13 +364,13 @@ static int usb_vcp_class_setup(USBD_HandleTypeDef *dev, usb_vcp_state_t *state, if ((req->bmRequest & USB_REQ_DIR_MASK) == USB_REQ_DIR_D2H) { if (req->bRequest == USB_CDC_GET_LINE_CODING) { - USBD_CtlSendData(dev, UNCONST(&line_coding), MIN(req->wLength, sizeof(line_coding))); + USBD_CtlSendData(dev, UNCONST(&line_coding), MIN_8bits(req->wLength, sizeof(line_coding))); } else { - USBD_CtlSendData(dev, state->cmd_buffer, MIN(req->wLength, USB_CDC_MAX_CMD_PACKET_LEN)); + USBD_CtlSendData(dev, state->cmd_buffer, MIN_8bits(req->wLength, USB_CDC_MAX_CMD_PACKET_LEN)); } } else { // USB_REQ_DIR_H2D if (req->wLength > 0) { - USBD_CtlPrepareRx(dev, state->cmd_buffer, MIN(req->wLength, USB_CDC_MAX_CMD_PACKET_LEN)); + USBD_CtlPrepareRx(dev, state->cmd_buffer, MIN_8bits(req->wLength, USB_CDC_MAX_CMD_PACKET_LEN)); } } diff --git a/embed/trezorhal/usbd_def.h b/embed/trezorhal/usbd_def.h index d3c3e5b991..e3da6243db 100644 --- a/embed/trezorhal/usbd_def.h +++ b/embed/trezorhal/usbd_def.h @@ -263,9 +263,12 @@ typedef struct _USBD_HandleTypeDef #define LOBYTE(x) ((uint8_t)(x & 0x00FF)) #define HIBYTE(x) ((uint8_t)((x & 0xFF00) >>8)) +#ifndef MIN #define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif +#ifndef MAX #define MAX(a, b) (((a) > (b)) ? (a) : (b)) - +#endif #if defined ( __GNUC__ ) #ifndef __weak diff --git a/embed/unix/common.h b/embed/unix/common.h index fd6c959495..e0bc6dde8f 100644 --- a/embed/unix/common.h +++ b/embed/unix/common.h @@ -27,10 +27,10 @@ #define STR(s) #s #ifndef MIN -#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MIN(a, b) ({ typeof(a) _a = (a); typeof(b) _b = (b); _a < _b ? _a : _b; }) #endif #ifndef MAX -#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define MAX(a, b) ({ typeof(a) _a = (a); typeof(b) _b = (b); _a > _b ? _a : _b; }) #endif void __attribute__((noreturn)) __fatal_error(const char *expr, const char *msg, const char *file, int line, const char *func); From 3055633d84d96d64bb3aee12fcf8cb21d461a809 Mon Sep 17 00:00:00 2001 From: Pavol Rusnak Date: Thu, 21 Feb 2019 13:27:28 +0100 Subject: [PATCH 02/15] vendor: update trezor-crypto and trezor-storage --- vendor/trezor-crypto | 2 +- vendor/trezor-storage | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vendor/trezor-crypto b/vendor/trezor-crypto index 21391dc5be..4211ce389f 160000 --- a/vendor/trezor-crypto +++ b/vendor/trezor-crypto @@ -1 +1 @@ -Subproject commit 21391dc5be9917bc32a518cf98376f79103727af +Subproject commit 4211ce389f6795d844809b0ba66a84082038ca04 diff --git a/vendor/trezor-storage b/vendor/trezor-storage index e55737c4b1..a109cc26c0 160000 --- a/vendor/trezor-storage +++ b/vendor/trezor-storage @@ -1 +1 @@ -Subproject commit e55737c4b1648c619d654eb25fa06fe381c5a1d4 +Subproject commit a109cc26c066e4bbd72dd69dc0a5db05be67491e From 5d645ef61beda1075de134fd76a9fcfd5e6a3897 Mon Sep 17 00:00:00 2001 From: Pavol Rusnak Date: Thu, 21 Feb 2019 19:39:50 +0100 Subject: [PATCH 03/15] bootloader: refactor mpu settings --- embed/bootloader/main.c | 11 +++++----- embed/trezorhal/mpu.c | 48 ++++++++++++++++++++++++++--------------- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/embed/bootloader/main.c b/embed/bootloader/main.c index f48d72cdda..a97607aa90 100644 --- a/embed/bootloader/main.c +++ b/embed/bootloader/main.c @@ -225,17 +225,18 @@ static void check_bootloader_version(void) int main(void) { - mpu_config_bootloader(); + touch_init(); + touch_power_on(); -main_start: - display_clear(); + mpu_config_bootloader(); #if PRODUCTION check_bootloader_version(); #endif - touch_init(); - touch_power_on(); +main_start: + + display_clear(); // delay to detect touch uint32_t touched = 0; diff --git a/embed/trezorhal/mpu.c b/embed/trezorhal/mpu.c index 05e0453350..03effbb851 100644 --- a/embed/trezorhal/mpu.c +++ b/embed/trezorhal/mpu.c @@ -40,30 +40,36 @@ void mpu_config_bootloader(void) // Note: later entries overwrite previous ones - // Flash (0x08000000 - 0x081FFFFF, 2 MiB, read-write) - MPU->RBAR = FLASH_BASE | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER0; - MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_FLASH | LL_MPU_REGION_SIZE_2MB | LL_MPU_REGION_PRIV_RO_URO; + // Everything (0x00000000 - 0xFFFFFFFF, 4 GiB, read-write) + MPU->RNR = MPU_REGION_NUMBER0; + MPU->RBAR = 0; + MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_FLASH | LL_MPU_REGION_SIZE_4GB | LL_MPU_REGION_FULL_ACCESS; // Flash (0x0800C000 - 0x0800FFFF, 16 KiB, no access) - MPU->RBAR = FLASH_BASE | 0xC000 | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER1; + MPU->RNR = MPU_REGION_NUMBER1; + MPU->RBAR = FLASH_BASE + 0xC000; MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_FLASH | LL_MPU_REGION_SIZE_16KB | LL_MPU_REGION_NO_ACCESS; // Flash (0x0810C000 - 0x0810FFFF, 16 KiB, no access) - MPU->RBAR = FLASH_BASE | 0x10C000 | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER2; + MPU->RNR = MPU_REGION_NUMBER2; + MPU->RBAR = FLASH_BASE + 0x10C000; MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_FLASH | LL_MPU_REGION_SIZE_16KB | LL_MPU_REGION_NO_ACCESS; // SRAM (0x20000000 - 0x2002FFFF, 192 KiB = 256 KiB except 2/8 at end, read-write, execute never) - MPU->RBAR = SRAM_BASE | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER3; + MPU->RNR = MPU_REGION_NUMBER3; + MPU->RBAR = SRAM_BASE; MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_SRAM | LL_MPU_REGION_SIZE_256KB | LL_MPU_REGION_FULL_ACCESS | MPU_RASR_XN_Msk | MPU_SUBREGION_DISABLE(0xC0); // Peripherals (0x40000000 - 0x5FFFFFFF, read-write, execute never) // External RAM (0x60000000 - 0x7FFFFFFF, read-write, execute never) - MPU->RBAR = PERIPH_BASE | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER4; + MPU->RNR = MPU_REGION_NUMBER4; + MPU->RBAR = PERIPH_BASE; MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_PERIPH | LL_MPU_REGION_SIZE_1GB | LL_MPU_REGION_FULL_ACCESS | MPU_RASR_XN_Msk; #ifdef STM32F427xx // CCMRAM (0x10000000 - 0x1000FFFF, read-write, execute never) - MPU->RBAR = CCMDATARAM_BASE | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER5; + MPU->RNR = MPU_REGION_NUMBER5; + MPU->RBAR = CCMDATARAM_BASE; MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_SRAM | LL_MPU_REGION_SIZE_64KB | LL_MPU_REGION_FULL_ACCESS | MPU_RASR_XN_Msk; #elif STM32F405xx // no CCMRAM @@ -85,41 +91,49 @@ void mpu_config_firmware(void) /* // Boardloader (0x08000000 - 0x0800FFFF, 64 KiB, read-only, execute never) - MPU->RBAR = FLASH_BASE | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER0; + MPU->RBAR = FLASH_BASE | MPU_REGION_NUMBER0; MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_FLASH | LL_MPU_REGION_SIZE_64KB | LL_MPU_REGION_PRIV_RO_URO | MPU_RASR_XN_Msk; */ // Bootloader (0x08020000 - 0x0803FFFF, 64 KiB, read-only) - MPU->RBAR = FLASH_BASE | 0x20000 | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER0; + MPU->RNR = MPU_REGION_NUMBER0; + MPU->RBAR = FLASH_BASE + 0x20000; MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_FLASH | LL_MPU_REGION_SIZE_64KB | LL_MPU_REGION_PRIV_RO_URO; // Storage#1 (0x08010000 - 0x0801FFFF, 64 KiB, read-write, execute never) - MPU->RBAR = FLASH_BASE | 0x10000 | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER1; + MPU->RNR = MPU_REGION_NUMBER1; + MPU->RBAR = FLASH_BASE + 0x10000; MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_FLASH | LL_MPU_REGION_SIZE_64KB | LL_MPU_REGION_FULL_ACCESS | MPU_RASR_XN_Msk; // Storage#2 (0x08110000 - 0x0811FFFF, 64 KiB, read-write, execute never) - MPU->RBAR = FLASH_BASE | 0x110000 | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER2; + MPU->RNR = MPU_REGION_NUMBER2; + MPU->RBAR = FLASH_BASE + 0x110000; MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_FLASH | LL_MPU_REGION_SIZE_64KB | LL_MPU_REGION_FULL_ACCESS | MPU_RASR_XN_Msk; // Firmware (0x08040000 - 0x080FFFFF, 6 * 128 KiB = 1024 KiB except 2/8 at start = 768 KiB, read-only) - MPU->RBAR = FLASH_BASE | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER3; + MPU->RNR = MPU_REGION_NUMBER3; + MPU->RBAR = FLASH_BASE; MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_FLASH | LL_MPU_REGION_SIZE_1MB | LL_MPU_REGION_PRIV_RO_URO | MPU_SUBREGION_DISABLE(0x03); // Firmware extra (0x08120000 - 0x081FFFFF, 7 * 128 KiB = 1024 KiB except 1/8 at start = 896 KiB, read-only) - MPU->RBAR = FLASH_BASE | 0x100000 | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER4; + MPU->RNR = MPU_REGION_NUMBER4; + MPU->RBAR = FLASH_BASE + 0x100000; MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_FLASH | LL_MPU_REGION_SIZE_1MB | LL_MPU_REGION_PRIV_RO_URO | MPU_SUBREGION_DISABLE(0x01); // SRAM (0x20000000 - 0x2002FFFF, 192 KiB = 256 KiB except 2/8 at end, read-write, execute never) - MPU->RBAR = SRAM_BASE | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER5; + MPU->RNR = MPU_REGION_NUMBER5; + MPU->RBAR = SRAM_BASE; MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_SRAM | LL_MPU_REGION_SIZE_256KB | LL_MPU_REGION_FULL_ACCESS | MPU_RASR_XN_Msk | MPU_SUBREGION_DISABLE(0xC0); // Peripherals (0x40000000 - 0x5FFFFFFF, read-write, execute never) // External RAM (0x60000000 - 0x7FFFFFFF, read-write, execute never) - MPU->RBAR = PERIPH_BASE | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER6; + MPU->RNR = MPU_REGION_NUMBER6; + MPU->RBAR = PERIPH_BASE; MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_PERIPH | LL_MPU_REGION_SIZE_1GB | LL_MPU_REGION_FULL_ACCESS | MPU_RASR_XN_Msk; #ifdef STM32F427xx // CCMRAM (0x10000000 - 0x1000FFFF, read-write, execute never) - MPU->RBAR = CCMDATARAM_BASE | MPU_RBAR_VALID_Msk | MPU_REGION_NUMBER7; + MPU->RNR = MPU_REGION_NUMBER7; + MPU->RBAR = CCMDATARAM_BASE; MPU->RASR = MPU_RASR_ENABLE_Msk | MPU_RASR_ATTR_SRAM | LL_MPU_REGION_SIZE_64KB | LL_MPU_REGION_FULL_ACCESS | MPU_RASR_XN_Msk; #elif STM32F405xx // no CCMRAM From 62c0e91dd036cf8ddfa6956343e60968e2a5967b Mon Sep 17 00:00:00 2001 From: Pavol Rusnak Date: Thu, 21 Feb 2019 21:44:11 +0100 Subject: [PATCH 04/15] firmware: bundle new bootloader 2.0.3 --- embed/firmware/bl_check.c | 8 ++++++-- embed/firmware/bootloader.bin | Bin 93696 -> 94208 bytes 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/embed/firmware/bl_check.c b/embed/firmware/bl_check.c index 6a967b53a3..3642e325c0 100644 --- a/embed/firmware/bl_check.c +++ b/embed/firmware/bl_check.c @@ -39,15 +39,19 @@ static secbool known_bootloader(const uint8_t *hash, int len) { // bootloader-2.0.2.bin (padded with 0xff) if (0 == memcmp(hash, "\xcc\x6b\x35\xc3\x8f\x29\x5c\xbd\x7d\x31\x69\xaf\xae\xf1\x61\x01\xef\xbe\x9f\x3b\x0a\xfd\xc5\x91\x70\x9b\xf5\xa0\xd5\xa4\xc5\xe0", 32)) return sectrue; return secfalse; + // bootloader-2.0.3.bin (padded with 0x00) + if (0 == memcmp(hash, "\xf9\xf3\x87\xbc\xd4\x7e\x9f\xdc\x6d\x97\xe7\x84\x3e\x7d\x87\x3b\x08\x43\x43\x63\xe2\x47\x71\x68\xe0\x40\xba\x1f\x21\x7f\xe2\x32", 32)) return sectrue; + // bootloader-2.0.3.bin (padded with 0xff) + if (0 == memcmp(hash, "\x2b\x58\x9d\x79\xcd\xe2\xe4\x3f\xe3\x14\x40\xb5\x41\x34\xa9\x94\xb4\xd5\xb9\x20\x12\x30\xd7\x15\xec\xda\x6f\x86\x18\x75\x23\xc8", 32)) return sectrue; } */ static secbool latest_bootloader(const uint8_t *hash, int len) { if (len != 32) return secfalse; // bootloader.bin (padded with 0x00) - if (0 == memcmp(hash, "\x2e\xf7\x47\xf8\x49\x87\x1e\xc8\xc6\x01\x35\xd6\x32\xe5\x5a\xd1\x56\x18\xf8\x64\x87\xb7\xaa\x7c\x62\x0e\xc3\x0d\x25\x69\x4e\x18", 32)) return sectrue; + if (0 == memcmp(hash, "\xf9\xf3\x87\xbc\xd4\x7e\x9f\xdc\x6d\x97\xe7\x84\x3e\x7d\x87\x3b\x08\x43\x43\x63\xe2\x47\x71\x68\xe0\x40\xba\x1f\x21\x7f\xe2\x32", 32)) return sectrue; // bootloader.bin (padded with 0xff) - if (0 == memcmp(hash, "\xcc\x6b\x35\xc3\x8f\x29\x5c\xbd\x7d\x31\x69\xaf\xae\xf1\x61\x01\xef\xbe\x9f\x3b\x0a\xfd\xc5\x91\x70\x9b\xf5\xa0\xd5\xa4\xc5\xe0", 32)) return sectrue; + if (0 == memcmp(hash, "\x2b\x58\x9d\x79\xcd\xe2\xe4\x3f\xe3\x14\x40\xb5\x41\x34\xa9\x94\xb4\xd5\xb9\x20\x12\x30\xd7\x15\xec\xda\x6f\x86\x18\x75\x23\xc8", 32)) return sectrue; return secfalse; } diff --git a/embed/firmware/bootloader.bin b/embed/firmware/bootloader.bin index 4f34cb118c1a3f8ae01b05c14b7b2c8c93bd1183..952056c46cc2420bc103668fd343837ea41cb509 100644 GIT binary patch delta 40712 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 From cbe8320d96ab92d65a2fe3adca69e9a31c716cc2 Mon Sep 17 00:00:00 2001 From: Pavol Rusnak Date: Fri, 22 Feb 2019 16:22:40 +0100 Subject: [PATCH 05/15] vendor: update trezor-storage --- vendor/trezor-storage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/trezor-storage b/vendor/trezor-storage index a109cc26c0..24df1ca2b7 160000 --- a/vendor/trezor-storage +++ b/vendor/trezor-storage @@ -1 +1 @@ -Subproject commit a109cc26c066e4bbd72dd69dc0a5db05be67491e +Subproject commit 24df1ca2b768d62162e9ab95602698d26c371746 From 3d82cca381fbf782b7bd83d8ea788e1db0d2ac18 Mon Sep 17 00:00:00 2001 From: Pavol Rusnak Date: Fri, 22 Feb 2019 17:22:46 +0100 Subject: [PATCH 06/15] trezorhal: refactor erasing sectors --- embed/boardloader/main.c | 8 ++------ embed/bootloader/main.c | 10 +++------- embed/bootloader/messages.c | 30 +++++------------------------- embed/bootloader/messages.h | 1 - embed/trezorhal/flash.c | 21 +++++++++++++++++++++ embed/trezorhal/flash.h | 7 +++++++ embed/trezorhal/image.h | 3 --- embed/unix/flash.c | 21 +++++++++++++++++++++ 8 files changed, 59 insertions(+), 42 deletions(-) diff --git a/embed/boardloader/main.c b/embed/boardloader/main.c index d5ff4a4993..34db7c35b1 100644 --- a/embed/boardloader/main.c +++ b/embed/boardloader/main.c @@ -105,9 +105,9 @@ static secbool copy_sdcard(void) // erase all flash (except boardloader) static const uint8_t sectors[] = { - 3, FLASH_SECTOR_STORAGE_1, FLASH_SECTOR_STORAGE_2, + 3, FLASH_SECTOR_BOOTLOADER, FLASH_SECTOR_FIRMWARE_START, 7, @@ -168,12 +168,8 @@ int main(void) periph_init(); if (sectrue != flash_configure_option_bytes()) { - static const uint8_t sectors[] = { - FLASH_SECTOR_STORAGE_1, - FLASH_SECTOR_STORAGE_2, - }; // display is not initialized so don't call ensure - secbool r = flash_erase_sectors(sectors, sizeof(sectors), NULL); + secbool r = flash_erase_sectors(STORAGE_SECTORS, STORAGE_SECTORS_COUNT, NULL); (void)r; return 2; } diff --git a/embed/bootloader/main.c b/embed/bootloader/main.c index a97607aa90..26e3285a77 100644 --- a/embed/bootloader/main.c +++ b/embed/bootloader/main.c @@ -262,7 +262,7 @@ main_start: firmware_present = load_image_header((const uint8_t *)(FIRMWARE_START + vhdr.hdrlen), FIRMWARE_IMAGE_MAGIC, FIRMWARE_IMAGE_MAXSIZE, vhdr.vsig_m, vhdr.vsig_n, vhdr.vpub, &hdr); } if (sectrue == firmware_present) { - firmware_present = check_image_contents(&hdr, IMAGE_HEADER_SIZE + vhdr.hdrlen, firmware_sectors, FIRMWARE_SECTORS_COUNT); + firmware_present = check_image_contents(&hdr, IMAGE_HEADER_SIZE + vhdr.hdrlen, FIRMWARE_SECTORS, FIRMWARE_SECTORS_COUNT); } // start the bootloader if no or broken firmware found ... @@ -286,11 +286,7 @@ main_start: ui_fadein(); // erase storage - static const uint8_t sectors_storage[] = { - FLASH_SECTOR_STORAGE_1, - FLASH_SECTOR_STORAGE_2, - }; - ensure(flash_erase_sectors(sectors_storage, sizeof(sectors_storage), NULL), NULL); + ensure(flash_erase_sectors(STORAGE_SECTORS, STORAGE_SECTORS_COUNT, NULL), NULL); // and start the usb loop if (bootloader_usb_loop(NULL, NULL) != sectrue) { @@ -353,7 +349,7 @@ main_start: "invalid firmware header"); ensure( - check_image_contents(&hdr, IMAGE_HEADER_SIZE + vhdr.hdrlen, firmware_sectors, FIRMWARE_SECTORS_COUNT), + check_image_contents(&hdr, IMAGE_HEADER_SIZE + vhdr.hdrlen, FIRMWARE_SECTORS, FIRMWARE_SECTORS_COUNT), "invalid firmware hash"); // if all VTRUST flags are unset = ultimate trust => skip the procedure diff --git a/embed/bootloader/messages.c b/embed/bootloader/messages.c index 70cf066a19..faa0902811 100644 --- a/embed/bootloader/messages.c +++ b/embed/bootloader/messages.c @@ -39,22 +39,6 @@ #define MSG_HEADER1_LEN 9 #define MSG_HEADER2_LEN 1 -const uint8_t firmware_sectors[FIRMWARE_SECTORS_COUNT] = { - FLASH_SECTOR_FIRMWARE_START, - 7, - 8, - 9, - 10, - FLASH_SECTOR_FIRMWARE_END, - FLASH_SECTOR_FIRMWARE_EXTRA_START, - 18, - 19, - 20, - 21, - 22, - FLASH_SECTOR_FIRMWARE_EXTRA_END, -}; - secbool msg_parse_header(const uint8_t *buf, uint16_t *msg_id, uint32_t *msg_size) { if (buf[0] != '?' || buf[1] != '#' || buf[2] != '#') { @@ -479,13 +463,9 @@ int process_msg_FirmwareUpload(uint8_t iface_num, uint32_t msg_size, uint8_t *bu // if firmware is not upgrade, erase storage if (sectrue != is_upgrade) { - static const uint8_t sectors_storage[] = { - FLASH_SECTOR_STORAGE_1, - FLASH_SECTOR_STORAGE_2, - }; - ensure(flash_erase_sectors(sectors_storage, sizeof(sectors_storage), NULL), NULL); + ensure(flash_erase_sectors(STORAGE_SECTORS, STORAGE_SECTORS_COUNT, NULL), NULL); } - ensure(flash_erase_sectors(firmware_sectors, FIRMWARE_SECTORS_COUNT, ui_screen_install_progress_erase), NULL); + ensure(flash_erase_sectors(FIRMWARE_SECTORS, FIRMWARE_SECTORS_COUNT, ui_screen_install_progress_erase), NULL); firstskip = IMAGE_HEADER_SIZE + vhdr.hdrlen; } @@ -521,7 +501,7 @@ int process_msg_FirmwareUpload(uint8_t iface_num, uint32_t msg_size, uint8_t *bu const uint32_t * const src = (const uint32_t * const)chunk_buffer; for (int i = 0; i < chunk_size / sizeof(uint32_t); i++) { - ensure(flash_write_word(firmware_sectors[firmware_block], i * sizeof(uint32_t), src[i]), NULL); + ensure(flash_write_word(FIRMWARE_SECTORS[firmware_block], i * sizeof(uint32_t), src[i]), NULL); } ensure(flash_lock_write(), NULL); @@ -546,9 +526,9 @@ int process_msg_FirmwareUpload(uint8_t iface_num, uint32_t msg_size, uint8_t *bu int process_msg_WipeDevice(uint8_t iface_num, uint32_t msg_size, uint8_t *buf) { static const uint8_t sectors[] = { - 3, FLASH_SECTOR_STORAGE_1, FLASH_SECTOR_STORAGE_2, + // 3, // skip because of MPU protection FLASH_SECTOR_FIRMWARE_START, 7, 8, @@ -558,7 +538,7 @@ int process_msg_WipeDevice(uint8_t iface_num, uint32_t msg_size, uint8_t *buf) FLASH_SECTOR_UNUSED_START, 13, 14, - FLASH_SECTOR_UNUSED_END, + // FLASH_SECTOR_UNUSED_END, // skip because of MPU protection FLASH_SECTOR_FIRMWARE_EXTRA_START, 18, 19, diff --git a/embed/bootloader/messages.h b/embed/bootloader/messages.h index 1c0d514ee0..d509dd9e13 100644 --- a/embed/bootloader/messages.h +++ b/embed/bootloader/messages.h @@ -28,7 +28,6 @@ #define USB_PACKET_SIZE 64 #define FIRMWARE_UPLOAD_CHUNK_RETRY_COUNT 2 -extern const uint8_t firmware_sectors[FIRMWARE_SECTORS_COUNT]; secbool msg_parse_header(const uint8_t *buf, uint16_t *msg_id, uint32_t *msg_size); diff --git a/embed/trezorhal/flash.c b/embed/trezorhal/flash.c index b270b7cc56..b91c2c8f96 100644 --- a/embed/trezorhal/flash.c +++ b/embed/trezorhal/flash.c @@ -54,6 +54,27 @@ static const uint32_t FLASH_SECTOR_TABLE[FLASH_SECTOR_COUNT + 1] = { [24] = 0x08200000, // last element - not a valid sector }; +const uint8_t FIRMWARE_SECTORS [FIRMWARE_SECTORS_COUNT] = { + FLASH_SECTOR_FIRMWARE_START, + 7, + 8, + 9, + 10, + FLASH_SECTOR_FIRMWARE_END, + FLASH_SECTOR_FIRMWARE_EXTRA_START, + 18, + 19, + 20, + 21, + 22, + FLASH_SECTOR_FIRMWARE_EXTRA_END, +}; + +const uint8_t STORAGE_SECTORS[STORAGE_SECTORS_COUNT] = { + FLASH_SECTOR_STORAGE_1, + FLASH_SECTOR_STORAGE_2, +}; + void flash_init(void) { } diff --git a/embed/trezorhal/flash.h b/embed/trezorhal/flash.h index dd707cf90f..285a1e76fc 100644 --- a/embed/trezorhal/flash.h +++ b/embed/trezorhal/flash.h @@ -60,6 +60,13 @@ // 22 #define FLASH_SECTOR_FIRMWARE_EXTRA_END 23 +#define BOOTLOADER_SECTORS_COUNT (1) +#define STORAGE_SECTORS_COUNT (2) +#define FIRMWARE_SECTORS_COUNT (6 + 7) + +extern const uint8_t STORAGE_SECTORS[STORAGE_SECTORS_COUNT]; +extern const uint8_t FIRMWARE_SECTORS[FIRMWARE_SECTORS_COUNT]; + // note: FLASH_SR_RDERR is STM32F42xxx and STM32F43xxx specific (STM32F427) (reference RM0090 section 3.7.5) #ifndef STM32F427xx #define FLASH_SR_RDERR 0 diff --git a/embed/trezorhal/image.h b/embed/trezorhal/image.h index c9153bf97f..7eef845317 100644 --- a/embed/trezorhal/image.h +++ b/embed/trezorhal/image.h @@ -27,9 +27,6 @@ #define BOOTLOADER_START 0x08020000 #define FIRMWARE_START 0x08040000 -#define BOOTLOADER_SECTORS_COUNT (1) -#define FIRMWARE_SECTORS_COUNT (6 + 7) - #define IMAGE_HEADER_SIZE 0x400 #define IMAGE_SIG_SIZE 65 #define IMAGE_CHUNK_SIZE (128 * 1024) diff --git a/embed/unix/flash.c b/embed/unix/flash.c index 4e68a99538..8e4febe6ab 100644 --- a/embed/unix/flash.c +++ b/embed/unix/flash.c @@ -62,6 +62,27 @@ static const uint32_t FLASH_SECTOR_TABLE[FLASH_SECTOR_COUNT + 1] = { [24] = 0x08200000, // last element - not a valid sector }; +const uint8_t FIRMWARE_SECTORS[FIRMWARE_SECTORS_COUNT] = { + FLASH_SECTOR_FIRMWARE_START, + 7, + 8, + 9, + 10, + FLASH_SECTOR_FIRMWARE_END, + FLASH_SECTOR_FIRMWARE_EXTRA_START, + 18, + 19, + 20, + 21, + 22, + FLASH_SECTOR_FIRMWARE_EXTRA_END, +}; + +const uint8_t STORAGE_SECTORS[STORAGE_SECTORS_COUNT] = { + FLASH_SECTOR_STORAGE_1, + FLASH_SECTOR_STORAGE_2, +}; + static uint8_t *FLASH_BUFFER; static uint32_t FLASH_SIZE; From 456a2c68d6fe794e37027c50526e403a78ba2874 Mon Sep 17 00:00:00 2001 From: Andrew Kozlik Date: Fri, 22 Feb 2019 21:53:37 +0100 Subject: [PATCH 07/15] pin: change show_pin_timeout() to display arbitrary message Don't pre-check old PIN when removing PIN protection. --- embed/extmod/modtrezorconfig/modtrezorconfig.c | 10 ++++++++-- src/apps/management/change_pin.py | 6 ++++-- src/trezor/pin.py | 4 ++-- vendor/trezor-storage | 2 +- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/embed/extmod/modtrezorconfig/modtrezorconfig.c b/embed/extmod/modtrezorconfig/modtrezorconfig.c index 5d84d655b2..6dbe13e5a9 100644 --- a/embed/extmod/modtrezorconfig/modtrezorconfig.c +++ b/embed/extmod/modtrezorconfig/modtrezorconfig.c @@ -17,6 +17,8 @@ * along with this program. If not, see . */ +#include + #include "py/runtime.h" #include "py/mphal.h" #include "py/objstr.h" @@ -31,9 +33,13 @@ STATIC mp_obj_t ui_wait_callback = mp_const_none; -STATIC secbool wrapped_ui_wait_callback(uint32_t wait, uint32_t progress) { +STATIC secbool wrapped_ui_wait_callback(uint32_t wait, uint32_t progress, const char* message) { if (mp_obj_is_callable(ui_wait_callback)) { - if (mp_call_function_2(ui_wait_callback, mp_obj_new_int(wait), mp_obj_new_int(progress)) == mp_const_true) { + mp_obj_t args[3]; + args[0] = mp_obj_new_int(wait); + args[1] = mp_obj_new_int(progress); + args[2] = mp_obj_new_str(message, strlen(message)); + if (mp_call_function_n_kw(ui_wait_callback, 3, 0, args) == mp_const_true) { return sectrue; } } diff --git a/src/apps/management/change_pin.py b/src/apps/management/change_pin.py index b85a7e0338..bff28395a1 100644 --- a/src/apps/management/change_pin.py +++ b/src/apps/management/change_pin.py @@ -17,8 +17,10 @@ async def change_pin(ctx, msg): # get current pin, return failure if invalid if config.has_pin(): curpin = await request_pin_ack(ctx, "Enter old PIN", config.get_pin_rem()) - if not config.check_pin(pin_to_int(curpin)): - raise wire.PinInvalid("PIN invalid") + # if removing, defer check to change_pin() + if not msg.remove: + if not config.check_pin(pin_to_int(curpin)): + raise wire.PinInvalid("PIN invalid") else: curpin = "" diff --git a/src/trezor/pin.py b/src/trezor/pin.py index eb4faa96df..5c62b69ffe 100644 --- a/src/trezor/pin.py +++ b/src/trezor/pin.py @@ -5,11 +5,11 @@ def pin_to_int(pin: str) -> int: return int("1" + pin) -def show_pin_timeout(seconds: int, progress: int) -> bool: +def show_pin_timeout(seconds: int, progress: int, message: str) -> bool: if progress == 0: ui.display.bar(0, 0, ui.WIDTH, ui.HEIGHT, ui.BG) ui.display.text_center( - ui.WIDTH // 2, 37, "Verifying PIN", ui.BOLD, ui.FG, ui.BG, ui.WIDTH + ui.WIDTH // 2, 37, message, ui.BOLD, ui.FG, ui.BG, ui.WIDTH ) ui.display.loader(progress, 0, ui.FG, ui.BG) if seconds == 0: diff --git a/vendor/trezor-storage b/vendor/trezor-storage index 24df1ca2b7..0e897f673a 160000 --- a/vendor/trezor-storage +++ b/vendor/trezor-storage @@ -1 +1 @@ -Subproject commit 24df1ca2b768d62162e9ab95602698d26c371746 +Subproject commit 0e897f673a2150607bae553e21604b253352644e From e60914e30f4d948008dda911844e6084d21195a6 Mon Sep 17 00:00:00 2001 From: Andrew Kozlik Date: Sat, 23 Feb 2019 01:56:56 +0100 Subject: [PATCH 08/15] pin: avoid changing PIN unnecessarily --- src/apps/management/recovery_device.py | 4 +++- src/apps/management/reset_device.py | 5 +++-- vendor/trezor-storage | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/apps/management/recovery_device.py b/src/apps/management/recovery_device.py index 07772b085b..271fc8de33 100644 --- a/src/apps/management/recovery_device.py +++ b/src/apps/management/recovery_device.py @@ -57,6 +57,8 @@ async def recovery_device(ctx, msg): # ask for pin repeatedly if msg.pin_protection: newpin = await request_pin_confirm(ctx, cancellable=False) + else: + newpin = "" # dry run if msg.dry_run: @@ -72,7 +74,7 @@ async def recovery_device(ctx, msg): ) # save into storage - if msg.pin_protection: + if newpin: config.change_pin(pin_to_int(""), pin_to_int(newpin)) storage.set_u2f_counter(msg.u2f_counter) storage.load_settings(label=msg.label, use_passphrase=msg.passphrase_protection) diff --git a/src/apps/management/reset_device.py b/src/apps/management/reset_device.py index d711a7342f..becc2c27f9 100644 --- a/src/apps/management/reset_device.py +++ b/src/apps/management/reset_device.py @@ -73,8 +73,9 @@ async def reset_device(ctx, msg): await show_wrong_entry(ctx) # write PIN into storage - if not config.change_pin(pin_to_int(""), pin_to_int(newpin)): - raise wire.ProcessError("Could not change PIN") + if newpin: + if not config.change_pin(pin_to_int(""), pin_to_int(newpin)): + raise wire.ProcessError("Could not change PIN") # write settings and mnemonic into storage storage.load_settings(label=msg.label, use_passphrase=msg.passphrase_protection) diff --git a/vendor/trezor-storage b/vendor/trezor-storage index 0e897f673a..511fc205b2 160000 --- a/vendor/trezor-storage +++ b/vendor/trezor-storage @@ -1 +1 @@ -Subproject commit 0e897f673a2150607bae553e21604b253352644e +Subproject commit 511fc205b284605651348512c5c5c2c95a642fa1 From edae40e22cb19a51dc1a02ba6806fc89f6c350fe Mon Sep 17 00:00:00 2001 From: Andrew Kozlik Date: Sat, 23 Feb 2019 12:19:08 +0100 Subject: [PATCH 09/15] pin: do not allow the user to enter an empty PIN An empty PIN is used to signify PIN removal and T1 does not allow entering an empty PIN neither via web-wallet nor via trezorctl. --- src/apps/common/request_pin.py | 13 +++++++++++++ src/trezor/ui/button.py | 3 +++ 2 files changed, 16 insertions(+) diff --git a/src/apps/common/request_pin.py b/src/apps/common/request_pin.py index 82eb3eb77b..c0d9f091c2 100644 --- a/src/apps/common/request_pin.py +++ b/src/apps/common/request_pin.py @@ -36,6 +36,17 @@ async def request_pin( c.taint() c.render() + c = dialog.confirm + if matrix.pin: + if not c.is_enabled(): + c.enable() + c.taint() + else: + if c.is_enabled(): + c.disable() + c.taint() + c.render() + if label is None: label = "Enter your PIN" sublabel = None @@ -59,6 +70,8 @@ async def request_pin( else: result = await dialog if result == CONFIRMED: + if not matrix.pin: + continue return matrix.pin elif matrix.pin: # reset matrix.change("") diff --git a/src/trezor/ui/button.py b/src/trezor/ui/button.py index 5d39410a77..75f5e427d2 100644 --- a/src/trezor/ui/button.py +++ b/src/trezor/ui/button.py @@ -36,6 +36,9 @@ class Button(Widget): self.state = BTN_DISABLED self.tainted = True + def is_enabled(self): + return self.state != BTN_DISABLED + def render(self): if not self.tainted: return From 2311465fdda2d7cd7aff6c4144c73df00cb0653c Mon Sep 17 00:00:00 2001 From: Pavol Rusnak Date: Sun, 24 Feb 2019 17:22:10 +0100 Subject: [PATCH 10/15] firmware: update embedded bootloader --- embed/firmware/bl_check.c | 10 +++++----- embed/firmware/bootloader.bin | Bin 94208 -> 94208 bytes 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/embed/firmware/bl_check.c b/embed/firmware/bl_check.c index 3642e325c0..65daf9b11f 100644 --- a/embed/firmware/bl_check.c +++ b/embed/firmware/bl_check.c @@ -38,20 +38,20 @@ static secbool known_bootloader(const uint8_t *hash, int len) { if (0 == memcmp(hash, "\x2e\xf7\x47\xf8\x49\x87\x1e\xc8\xc6\x01\x35\xd6\x32\xe5\x5a\xd1\x56\x18\xf8\x64\x87\xb7\xaa\x7c\x62\x0e\xc3\x0d\x25\x69\x4e\x18", 32)) return sectrue; // bootloader-2.0.2.bin (padded with 0xff) if (0 == memcmp(hash, "\xcc\x6b\x35\xc3\x8f\x29\x5c\xbd\x7d\x31\x69\xaf\xae\xf1\x61\x01\xef\xbe\x9f\x3b\x0a\xfd\xc5\x91\x70\x9b\xf5\xa0\xd5\xa4\xc5\xe0", 32)) return sectrue; - return secfalse; // bootloader-2.0.3.bin (padded with 0x00) - if (0 == memcmp(hash, "\xf9\xf3\x87\xbc\xd4\x7e\x9f\xdc\x6d\x97\xe7\x84\x3e\x7d\x87\x3b\x08\x43\x43\x63\xe2\x47\x71\x68\xe0\x40\xba\x1f\x21\x7f\xe2\x32", 32)) return sectrue; + if (0 == memcmp(hash, "\xb1\x83\xd3\x31\xc7\xff\x3d\xcf\x54\x1e\x7e\x40\xf4\x9e\xc3\x53\x4c\xcc\xf3\x8c\x35\x39\x88\x81\x65\xc0\x5c\x25\xbd\xfc\xea\x14", 32)) return sectrue; // bootloader-2.0.3.bin (padded with 0xff) - if (0 == memcmp(hash, "\x2b\x58\x9d\x79\xcd\xe2\xe4\x3f\xe3\x14\x40\xb5\x41\x34\xa9\x94\xb4\xd5\xb9\x20\x12\x30\xd7\x15\xec\xda\x6f\x86\x18\x75\x23\xc8", 32)) return sectrue; + if (0 == memcmp(hash, "\xab\xdb\x7d\xe2\xef\x44\x66\xa7\xb7\x1f\x2b\x02\xf3\xe1\x40\xe7\xcd\xf2\x8e\xc0\xbb\x33\x04\xce\x0d\xa5\xca\x02\x57\xb6\xd4\x30", 32)) return sectrue; + return secfalse; } */ static secbool latest_bootloader(const uint8_t *hash, int len) { if (len != 32) return secfalse; // bootloader.bin (padded with 0x00) - if (0 == memcmp(hash, "\xf9\xf3\x87\xbc\xd4\x7e\x9f\xdc\x6d\x97\xe7\x84\x3e\x7d\x87\x3b\x08\x43\x43\x63\xe2\x47\x71\x68\xe0\x40\xba\x1f\x21\x7f\xe2\x32", 32)) return sectrue; + if (0 == memcmp(hash, "\xb1\x83\xd3\x31\xc7\xff\x3d\xcf\x54\x1e\x7e\x40\xf4\x9e\xc3\x53\x4c\xcc\xf3\x8c\x35\x39\x88\x81\x65\xc0\x5c\x25\xbd\xfc\xea\x14", 32)) return sectrue; // bootloader.bin (padded with 0xff) - if (0 == memcmp(hash, "\x2b\x58\x9d\x79\xcd\xe2\xe4\x3f\xe3\x14\x40\xb5\x41\x34\xa9\x94\xb4\xd5\xb9\x20\x12\x30\xd7\x15\xec\xda\x6f\x86\x18\x75\x23\xc8", 32)) return sectrue; + if (0 == memcmp(hash, "\xab\xdb\x7d\xe2\xef\x44\x66\xa7\xb7\x1f\x2b\x02\xf3\xe1\x40\xe7\xcd\xf2\x8e\xc0\xbb\x33\x04\xce\x0d\xa5\xca\x02\x57\xb6\xd4\x30", 32)) return sectrue; return secfalse; } diff --git a/embed/firmware/bootloader.bin b/embed/firmware/bootloader.bin index 952056c46cc2420bc103668fd343837ea41cb509..aebb4eb720fd1ae1d298b8d84b44c9635ec7013b 100644 GIT binary patch delta 1405 zcmZXSdr(w$6vywmcUSHzYrEjWG|U>*fNc*F!toErgb0jkc`hs(uSJ&S{aoT5tdNKc zC?04D%0`{+546SZ)anE=An26QV;rTT1hq!9G-q^jm59!TwPEOw&pqdN?)Uq`(KRJJ2UG?hL{==rGloR1^a^VaY*4j0F zA*diJ#}3~&#+CYLqqZd%KgUCl!d=aOD*mCOO8*Iui6>Q zSjf@u=j&Cy!UETm1B}p&K5%DIDze3T8HO1%E2~RM7lH0xiNCt(x+wK|J9;Pr`4wxUB~8^yPkZg5W>s6K^B$8@o_}1U??|%)IEdaLvgYq zf}J1PY<`@*WyECjZkc-y$BXav_^WwJ`G#?6b&&Q8&F1)SVY0G~h2KzL9SS!P ztMlSVN%EQ-qj;GRjUT(DakQSGTx@rmBEv}d6sp~!e4N@2rYJU+L1UTBW*0}X*1++_ zsI55x4{)JI1An8Y)}Nj8#JJk_E#34Xo$DiwBoxo{aFYYH{*Z7Idg?%&Yw0HeF#)eU zpF1`vjq<=p>ukyp8i9DkH55g+9!KkXHf&v-pQrOBaq5=EnUluPrO~>M*&sC3+gWM* zPLA)xphi1nl2%))mqn^~2POje@aWj;vph@ftze@=_@d+eaV<84z*ZlP_j1EkMU zKYbY4J_8!`?XPsadggz(O709K)G-yC?Hq|b`GYAMbV&Yi;BU3~y<7=#0hJNV#b z+y)9k({1wsoiJ+OELT_3JX?rPqL=u9uoEYVF;orD$?sBjm5|fa{RQ>sbjmZM+VKTAgW8G5{Jx;exOt7)aOs*v7lMjpU&|^ z#LtfC9pDD?bjtIIGGebI?RD^ifJ9wvLio1GjKs(Y$`YUPT~Wx0h)s&oIr>wfAD)Ve zPSEPZ6PBr0R+i)%#*vi{VXV!b2`!418e4<`cK^Tng8>THOBAczc-iVT?((%t56^Wf zFK=_Xv9_c7L% N;w4>ilB31I{tMe5&zJxJ delta 1407 zcmZXTe@qi+7{~kEwQ!wuwn!lhYbYqPX1F06C^2kf2+A@j0+R{S(n4EGfkF%6=LQIh zh%oS#mFbiPmxTln*loFyjUk{;2TY9vw=7XebZ$m665PZQ#-1y6G47AgJ@50p-{-mK zecrp8Sy9cbs8y-_qo!jbNa1tZoV-0C{ov%h`I{@t+PbAX%AdEJ{_K%ZgHi9(Q3AT< z`bx$^%guF{&rQ$g9&ZjQ*>Z0*IQ3(BNS{X2tMB?WK-1Ck>+gP-UO&&NeeRw5@nS;c zvDmJwZmAP15qp1%U{~pOBM8~{+*<&Fq$KYV>?78EU&tU$`8z;IzRM2;1DVfPQ<)m_ zT7h@qnf){~ggin#GiW9yDJ?bx?QWzilV)zC{hO2*TvzBf3jBo&9sdXihJRaV5R5E| z1yU(03`i|8TYI3C$O~T-`~DY;EBr-q*_qY5@?=)U z)MUyYP05Vp2fLDl76(w2k?bu#Bvu+}hTpf4JH28Ei9Ct|*(VxLJf86_M1p*=SF2;m9n%RfFgky|9m=?z}l}$NE z;fH(GB))tQD6(3v0-C5Q0;%;INn*v($ZmX5it8nFY{3)ob1nRG zbHe7gUdb675XE|t=PDht7~JJsd>$>IZ6TwT?Lo=T`E6L*$xpr3Idjy6x-@%SZPv2y zRrOP%PqJv{EZJJ!4`pPoy2<4i)i^|Bm~f3qYAeMQY8KJ+B5k#$Vm|JS5ZQqy%sO?T zNu)+aB-v8e;93pUjQ~f=>r>${d#!#OCC|a6PNdb9{5e{VRW#r?rz0ze`(YF5YrL_R z%{Ct4KopJVIZI-W-hxOH*lfjTdvgy&vQaI&06N*m50=4m5?{+#)uDZWcx1C5ww(a* zC0j|4v%tm?SK7t+|#{%sOPBhIsfpDEId3?bvlBr-$f!^;xJL^BW3^G6%O zD!#=o-(*)t2pZBhYKNVyXsih!hqR5W@NfBg{GenKr3(muaXBRZ{3;BR_zQmiU*KiT z-xUPiyes0bqmI9h*(bCN+kD}qP+XKlGhn6}?EL$O61~fdKw%vlHn9j$MO0H3JV^VL z4<)Oy;Fpp|Gsn;lNCeVuGJHh~f3b?Im2UD*EaQ<&GXY2(VnGP$oAH(0$DIMhAJaH? zdd5;G?$prCIb@EDO9OY1=y6?ulprl!Mml)I3wA?RvDIeR+VQC~8G@B|bA`cbd^)b; zKH3i_A=qfP<(KMg2CXYESQ#6a6rUQE6r)X!iB3@`C2OPA?| From bac8d9d5316bb15bc85200a5301cba4c9cb947eb Mon Sep 17 00:00:00 2001 From: Pavol Rusnak Date: Mon, 25 Feb 2019 12:28:06 +0100 Subject: [PATCH 11/15] changelog: update --- ChangeLog | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index bad297632c..1f0eb7c99a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,11 @@ -Version 2.1.0 [unreleased] +Version 2.1.0 * stable release, optional update +* Security improvements +* Upgraded to new storage format +* Ripple, Stellar, Cardano and NEM fixes +* New coins: ATS, AXE, FLO, GIN, KMD, NIX, + PIVX, REOSC, XPM, XSN, ZCL +* New ETH tokens Version 2.0.10 * stable release, optional update From c23ad89ee1b0bdcf6ed5cd276112e9ab363322be Mon Sep 17 00:00:00 2001 From: Andrew Kozlik Date: Wed, 27 Feb 2019 18:27:09 +0100 Subject: [PATCH 12/15] storage: Fix bug in U2F counter upgrade. --- src/apps/common/storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/common/storage.py b/src/apps/common/storage.py index 92c2a0d13c..e30f263dce 100644 --- a/src/apps/common/storage.py +++ b/src/apps/common/storage.py @@ -195,7 +195,7 @@ def init_unlocked(): counter = config.get(_APP, _U2F_COUNTER) if counter is not None: config.set_counter( - _APP, _U2F_COUNTER, counter, True + _APP, _U2F_COUNTER, int.from_bytes(counter, "big"), True ) # writable when locked config.delete(_APP, _U2F_COUNTER) config.set(_APP, _VERSION, _STORAGE_VERSION) From 9b97b9e840a6b20527e1677e6512198071dabc5f Mon Sep 17 00:00:00 2001 From: Jan Pochyla Date: Mon, 4 Mar 2019 17:31:58 +0100 Subject: [PATCH 13/15] Revert "scons: disable bip39 seed cache" This reverts commit 34f499fc0461975f698b0fe214f8c50da387eb90. --- SConscript.firmware | 1 - SConscript.unix | 1 - 2 files changed, 2 deletions(-) diff --git a/SConscript.firmware b/SConscript.firmware index 7b66b6fa68..fcc9dee2f9 100644 --- a/SConscript.firmware +++ b/SConscript.firmware @@ -29,7 +29,6 @@ CPPDEFINES_MOD += [ 'AES_128', 'AES_192', 'RAND_PLATFORM_INDEPENDENT', - ('USE_BIP39_CACHE', '0'), ('USE_KECCAK', '1'), ('USE_ETHEREUM', '1'), ('USE_MONERO', '1'), diff --git a/SConscript.unix b/SConscript.unix index a6b23ea024..3ba197eaa6 100644 --- a/SConscript.unix +++ b/SConscript.unix @@ -27,7 +27,6 @@ CPPPATH_MOD += [ CPPDEFINES_MOD += [ 'AES_128', 'AES_192', - ('USE_BIP39_CACHE', '0'), ('USE_KECCAK', '1'), ('USE_ETHEREUM', '1'), ('USE_MONERO', '1'), From 3f0e3a334e03f49ff819d14cbbec00194c586c27 Mon Sep 17 00:00:00 2001 From: Jan Pochyla Date: Tue, 5 Mar 2019 13:49:42 +0100 Subject: [PATCH 14/15] fido_u2f: properly close u2f layout when other layout starts As it's currently impossible to close() the layout generator from the outside, loop.signal() was added, and any started layout signals to terminate. --- src/apps/fido_u2f/__init__.py | 6 +++++- src/apps/wallet/sign_tx/__init__.py | 1 - src/trezor/loop.py | 3 +++ src/trezor/workflow.py | 4 ++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/apps/fido_u2f/__init__.py b/src/apps/fido_u2f/__init__.py index fae31981c6..1825a3d259 100644 --- a/src/apps/fido_u2f/__init__.py +++ b/src/apps/fido_u2f/__init__.py @@ -353,7 +353,7 @@ class ConfirmState: return False return True - def setup(self, action: int, checksum: bytes, app_id: bytes) -> None: + def setup(self, action: int, checksum: bytes, app_id: bytes) -> bool: if workflow.workflows: return False @@ -379,6 +379,10 @@ class ConfirmState: @ui.layout async def confirm_layout(self) -> None: + workflow.fido_u2f_stop_signal.reset() + await loop.spawn(self.confirm_layout_inner(), workflow.fido_u2f_stop_signal) + + async def confirm_layout_inner(self) -> None: from trezor.ui.confirm import ConfirmDialog, CONFIRMED from trezor.ui.text import Text diff --git a/src/apps/wallet/sign_tx/__init__.py b/src/apps/wallet/sign_tx/__init__.py index 2a6213fd6b..65f8bee85c 100644 --- a/src/apps/wallet/sign_tx/__init__.py +++ b/src/apps/wallet/sign_tx/__init__.py @@ -16,7 +16,6 @@ from apps.wallet.sign_tx import ( ) -@ui.layout async def sign_tx(ctx, msg, keychain): signer = signing.sign_tx(msg, keychain) diff --git a/src/trezor/loop.py b/src/trezor/loop.py index 8075afbbd1..7d25e5583f 100644 --- a/src/trezor/loop.py +++ b/src/trezor/loop.py @@ -185,6 +185,9 @@ class signal(Syscall): """ def __init__(self): + self.reset() + + def reset(self): self.value = _NO_VALUE self.task = None diff --git a/src/trezor/workflow.py b/src/trezor/workflow.py index 8b6d32d83d..7e11d4c32c 100644 --- a/src/trezor/workflow.py +++ b/src/trezor/workflow.py @@ -5,6 +5,9 @@ layouts = [] default = None default_layout = None +# HACK: workaround way to stop the u2f layout from the outside +fido_u2f_stop_signal = loop.signal() + def onstart(w): workflows.append(w) @@ -44,6 +47,7 @@ def restartdefault(): def onlayoutstart(l): closedefault() layouts.append(l) + fido_u2f_stop_signal.send(None) def onlayoutclose(l): From f13f065ac4f9f3b03f3ea445bddfecf492ce970d Mon Sep 17 00:00:00 2001 From: Pavol Rusnak Date: Wed, 6 Mar 2019 10:12:50 +0100 Subject: [PATCH 15/15] src/apps/wallet/sign_tx: fix style --- src/apps/wallet/sign_tx/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/wallet/sign_tx/__init__.py b/src/apps/wallet/sign_tx/__init__.py index 65f8bee85c..3c6fd7bc21 100644 --- a/src/apps/wallet/sign_tx/__init__.py +++ b/src/apps/wallet/sign_tx/__init__.py @@ -1,4 +1,4 @@ -from trezor import ui, wire +from trezor import wire from trezor.messages.MessageType import TxAck from trezor.messages.RequestType import TXFINISHED from trezor.messages.TxRequest import TxRequest