From 8d3d3be09c08f33682ad8415611a4ac7f9720c56 Mon Sep 17 00:00:00 2001 From: Ondrej Mikle Date: Fri, 21 Aug 2020 14:39:56 +0200 Subject: [PATCH] core/bootloader_ic: introduce CI bootloader for TT device tests (#1182) --- core/Makefile | 10 + core/SConscript.bootloader_ci | 197 ++++++ core/SConstruct | 1 + core/embed/bootloader_ci/ChangeLog | 26 + core/embed/bootloader_ci/README.md | 16 + .../bootloader_ci/bootloader_flash.jlink | 7 + core/embed/bootloader_ci/bootui.c | 1 + core/embed/bootloader_ci/bootui.h | 1 + core/embed/bootloader_ci/header.S | 1 + core/embed/bootloader_ci/icon_cancel.h | 1 + core/embed/bootloader_ci/icon_confirm.h | 1 + core/embed/bootloader_ci/icon_done.h | 1 + core/embed/bootloader_ci/icon_fail.h | 1 + core/embed/bootloader_ci/icon_info.h | 1 + core/embed/bootloader_ci/icon_install.h | 1 + core/embed/bootloader_ci/icon_logo.h | 1 + core/embed/bootloader_ci/icon_safeplace.h | 1 + core/embed/bootloader_ci/icon_welcome.h | 1 + core/embed/bootloader_ci/icon_wipe.h | 1 + core/embed/bootloader_ci/main.c | 308 +++++++++ core/embed/bootloader_ci/memory.ld | 1 + core/embed/bootloader_ci/messages.c | 654 ++++++++++++++++++ core/embed/bootloader_ci/messages.h | 1 + core/embed/bootloader_ci/protob | 1 + core/embed/bootloader_ci/startup.s | 1 + core/embed/bootloader_ci/version.h | 1 + 26 files changed, 1237 insertions(+) create mode 100644 core/SConscript.bootloader_ci create mode 100644 core/embed/bootloader_ci/ChangeLog create mode 100644 core/embed/bootloader_ci/README.md create mode 100644 core/embed/bootloader_ci/bootloader_flash.jlink create mode 120000 core/embed/bootloader_ci/bootui.c create mode 120000 core/embed/bootloader_ci/bootui.h create mode 120000 core/embed/bootloader_ci/header.S create mode 120000 core/embed/bootloader_ci/icon_cancel.h create mode 120000 core/embed/bootloader_ci/icon_confirm.h create mode 120000 core/embed/bootloader_ci/icon_done.h create mode 120000 core/embed/bootloader_ci/icon_fail.h create mode 120000 core/embed/bootloader_ci/icon_info.h create mode 120000 core/embed/bootloader_ci/icon_install.h create mode 120000 core/embed/bootloader_ci/icon_logo.h create mode 120000 core/embed/bootloader_ci/icon_safeplace.h create mode 120000 core/embed/bootloader_ci/icon_welcome.h create mode 120000 core/embed/bootloader_ci/icon_wipe.h create mode 100644 core/embed/bootloader_ci/main.c create mode 120000 core/embed/bootloader_ci/memory.ld create mode 100644 core/embed/bootloader_ci/messages.c create mode 120000 core/embed/bootloader_ci/messages.h create mode 120000 core/embed/bootloader_ci/protob create mode 120000 core/embed/bootloader_ci/startup.s create mode 120000 core/embed/bootloader_ci/version.h diff --git a/core/Makefile b/core/Makefile index b024dffa2..9400ffcd4 100644 --- a/core/Makefile +++ b/core/Makefile @@ -7,6 +7,7 @@ SCONS = scons -Q -j $(JOBS) BUILD_DIR = build BOARDLOADER_BUILD_DIR = $(BUILD_DIR)/boardloader BOOTLOADER_BUILD_DIR = $(BUILD_DIR)/bootloader +BOOTLOADER_CI_BUILD_DIR = $(BUILD_DIR)/bootloader_ci PRODTEST_BUILD_DIR = $(BUILD_DIR)/prodtest REFLASH_BUILD_DIR = $(BUILD_DIR)/reflash FIRMWARE_BUILD_DIR = $(BUILD_DIR)/firmware @@ -127,6 +128,9 @@ build_boardloader: ## build boardloader build_bootloader: ## build bootloader $(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" $(BOOTLOADER_BUILD_DIR)/bootloader.bin +build_bootloader_ci: ## build CI device testing bootloader + $(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" $(BOOTLOADER_CI_BUILD_DIR)/bootloader.bin + build_prodtest: ## build production test firmware $(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" $(PRODTEST_BUILD_DIR)/prodtest.bin @@ -186,6 +190,9 @@ flash_boardloader: $(BOARDLOADER_BUILD_DIR)/boardloader.bin ## flash boardloader flash_bootloader: $(BOOTLOADER_BUILD_DIR)/bootloader.bin ## flash bootloader using OpenOCD $(OPENOCD) -c "init; reset halt; flash write_image erase $< $(BOOTLOADER_START); exit" +flash_bootloader_ci: $(BOOTLOADER_CI_BUILD_DIR)/bootloader.bin ## flash CI bootloader using OpenOCD + $(OPENOCD) -c "init; reset halt; flash write_image erase $< $(BOOTLOADER_START); exit" + flash_prodtest: $(PRODTEST_BUILD_DIR)/prodtest.bin ## flash prodtest using OpenOCD $(OPENOCD) -c "init; reset halt; flash write_image erase $< $(PRODTEST_START); exit" @@ -210,6 +217,9 @@ flash_erase_storage: ## erase storage sectors from flash flash_bootloader_jlink: $(BOOTLOADER_BUILD_DIR)/bootloader.bin ## flash bootloader using JLink JLinkExe -commanderscript embed/bootloader/bootloader_flash.jlink +flash_bootloader_ci_jlink: $(BOOTLOADER_CI_BUILD_DIR)/bootloader.bin ## flash CI bootloader using JLink + JLinkExe -commanderscript embed/bootloader_ci/bootloader_flash.jlink + flash_firmware_jlink: $(FIRMWARE_BUILD_DIR)/firmware.bin ## flash firmware using JLink. file names must end in .bin for JLink cp -f $<.p1 $<.p1.bin cp -f $<.p2 $<.p2.bin diff --git a/core/SConscript.bootloader_ci b/core/SConscript.bootloader_ci new file mode 100644 index 000000000..0e4380927 --- /dev/null +++ b/core/SConscript.bootloader_ci @@ -0,0 +1,197 @@ +# pylint: disable=E0602 + +import os + +CCFLAGS_MOD = '' +CPPPATH_MOD = [] +CPPDEFINES_MOD = [] +SOURCE_MOD = [] + +# modtrezorcrypto +CCFLAGS_MOD += '-Wno-sequence-point ' +CPPPATH_MOD += [ + 'vendor/trezor-crypto', +] +CPPDEFINES_MOD += [ + 'AES_128', + 'AES_192', + 'USE_KECCAK', + 'ED25519_NO_PRECOMP', +] +SOURCE_MOD += [ + 'vendor/trezor-crypto/blake2s.c', + 'vendor/trezor-crypto/ed25519-donna/curve25519-donna-32bit.c', + 'vendor/trezor-crypto/ed25519-donna/curve25519-donna-helpers.c', + 'vendor/trezor-crypto/ed25519-donna/ed25519.c', + 'vendor/trezor-crypto/ed25519-donna/ed25519-donna-32bit-tables.c', + 'vendor/trezor-crypto/ed25519-donna/ed25519-donna-impl-base.c', + 'vendor/trezor-crypto/ed25519-donna/modm-donna-32bit.c', + 'vendor/trezor-crypto/hmac_drbg.c', + 'vendor/trezor-crypto/memzero.c', + 'vendor/trezor-crypto/rand.c', + 'vendor/trezor-crypto/sha2.c', +] + +# modtrezorui +CPPPATH_MOD += [ + 'vendor/micropython/extmod/uzlib', +] +CPPDEFINES_MOD += [ + 'TREZOR_FONT_NORMAL_ENABLE', + 'TREZOR_FONT_MONO_ENABLE', +] +SOURCE_MOD += [ + 'embed/extmod/modtrezorui/display.c', + 'embed/extmod/modtrezorui/font_bitmap.c', + 'embed/extmod/modtrezorui/font_roboto_regular_20.c', + 'embed/extmod/modtrezorui/font_robotomono_regular_20.c', + 'embed/extmod/modtrezorui/qr-code-generator/qrcodegen.c', + 'vendor/micropython/extmod/uzlib/adler32.c', + 'vendor/micropython/extmod/uzlib/crc32.c', + 'vendor/micropython/extmod/uzlib/tinflate.c', +] + +SOURCE_STMHAL = [ + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal.c', + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_cortex.c', + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash.c', + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash_ex.c', + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_gpio.c', + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c.c', + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pcd.c', + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pcd_ex.c', + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pwr.c', + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc.c', + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_sd.c', + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_spi.c', + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_sram.c', + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim.c', + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim_ex.c', + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_ll_fmc.c', + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_ll_sdmmc.c', + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_ll_usb.c', +] + +SOURCE_NANOPB = [ + 'vendor/nanopb/pb_common.c', + 'vendor/nanopb/pb_decode.c', + 'vendor/nanopb/pb_encode.c', +] + +SOURCE_BOOTLOADER = [ + 'embed/bootloader_ci/startup.s', + 'embed/bootloader_ci/header.S', + 'embed/bootloader_ci/bootui.c', + 'embed/bootloader_ci/main.c', + 'embed/bootloader_ci/messages.c', + 'embed/bootloader_ci/protob/messages.pb.c', +] + +SOURCE_TREZORHAL = [ + 'embed/trezorhal/common.c', + '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/systick.c', + 'embed/trezorhal/touch.c', + 'embed/trezorhal/usb.c', + 'embed/trezorhal/usbd_conf.c', + 'embed/trezorhal/usbd_core.c', + 'embed/trezorhal/usbd_ctlreq.c', + 'embed/trezorhal/usbd_ioreq.c', + 'embed/trezorhal/util.s', + 'embed/trezorhal/vectortable.s', +] + +env = Environment(ENV=os.environ, CFLAGS='%s -DPRODUCTION=%s' % (ARGUMENTS.get('CFLAGS', ''), ARGUMENTS.get('PRODUCTION', '0'))) + +env.Replace( + AS='arm-none-eabi-as', + AR='arm-none-eabi-ar', + CC='arm-none-eabi-gcc', + LINK='arm-none-eabi-gcc', + SIZE='arm-none-eabi-size', + STRIP='arm-none-eabi-strip', + OBJCOPY='arm-none-eabi-objcopy', ) + +env.Replace( + TREZOR_MODEL=env.get('ENV').get('TREZOR_MODEL', 'T'), ) + +if env.get('TREZOR_MODEL') == 'T': + CPU_ASFLAGS = '-mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16' + CPU_CCFLAGS = '-mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -mtune=cortex-m4 ' + CPU_MODEL = 'STM32F427xx' +elif env.get('TREZOR_MODEL') == '1': + CPU_ASFLAGS = '-mthumb -mcpu=cortex-m3 -mfloat-abi=soft' + CPU_CCFLAGS = '-mthumb -mtune=cortex-m3 -mcpu=cortex-m3 -mfloat-abi=soft ' + CPU_MODEL = 'STM32F405xx' +else: + raise ValueError('Unknown Trezor model') + +env.Replace( + COPT=env.get('ENV').get('OPTIMIZE', '-Og'), + CCFLAGS='$COPT ' + '-g3 ' + '-nostdlib ' + '-std=gnu99 -Wall -Werror -Wdouble-promotion -Wpointer-arith -Wno-missing-braces -fno-common ' + '-fsingle-precision-constant -fdata-sections -ffunction-sections ' + '-ffreestanding ' + '-fstack-protector-all ' + + CPU_CCFLAGS + CCFLAGS_MOD, + CCFLAGS_QSTR='-DNO_QSTR -DN_X64 -DN_X86 -DN_THUMB', + LINKFLAGS='-T embed/bootloader_ci/memory.ld -Wl,--gc-sections -Wl,-Map=build/bootloader_ci/bootloader.map -Wl,--warn-common', + CPPPATH=[ + 'embed/bootloader_ci', + 'embed/bootloader_ci/nanopb', + 'embed/bootloader_ci/protob', + 'embed/trezorhal', + 'embed/extmod/modtrezorui', + 'vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Inc', + 'vendor/micropython/lib/stm32lib/CMSIS/STM32F4xx/Include', + 'vendor/micropython/lib/cmsis/inc', + 'vendor/nanopb', + ] + CPPPATH_MOD, + CPPDEFINES=[ + ('TREZOR_MODEL', '$TREZOR_MODEL'), + CPU_MODEL, + 'USE_HAL_DRIVER', + ('STM32_HAL_H', '""'), + 'PB_FIELD_16BIT', + 'PB_ENCODE_ARRAYS_UNPACKED', + 'PB_VALIDATE_UTF8', + ] + CPPDEFINES_MOD, + ASFLAGS=CPU_ASFLAGS, + ASPPFLAGS='$CFLAGS $CCFLAGS', ) + +env.Replace( + HEADERTOOL='tools/headertool.py', +) + +# +# Program objects +# + +obj_program = [] +obj_program += env.Object(source=SOURCE_MOD) +obj_program += env.Object(source=SOURCE_BOOTLOADER) +obj_program += env.Object(source=SOURCE_NANOPB) +obj_program += env.Object(source=SOURCE_STMHAL) +obj_program += env.Object(source=SOURCE_TREZORHAL) + +program_elf = env.Command( + target='bootloader.elf', + source=obj_program, + action= + '$LINK -o $TARGET $CCFLAGS $CFLAGS $LINKFLAGS $SOURCES -lc_nano -lgcc', +) + +program_bin = env.Command( + target='bootloader.bin', + source=program_elf, + action=[ + '$OBJCOPY -O binary -j .header -j .flash -j .data $SOURCE $TARGET', + '$HEADERTOOL $TARGET ' + ('-D' if ARGUMENTS.get('PRODUCTION', '0') == '0' else ''), + ], ) diff --git a/core/SConstruct b/core/SConstruct index 4eccd20bc..0f05872d3 100644 --- a/core/SConstruct +++ b/core/SConstruct @@ -6,3 +6,4 @@ SConscript('SConscript.reflash', variant_dir='build/reflash', duplicate=False) SConscript('SConscript.prodtest', variant_dir='build/prodtest', duplicate=False) SConscript('SConscript.firmware', variant_dir='build/firmware', duplicate=False) SConscript('SConscript.unix', variant_dir='build/unix', duplicate=False) +SConscript('SConscript.bootloader_ci', variant_dir='build/bootloader_ci', duplicate=False) diff --git a/core/embed/bootloader_ci/ChangeLog b/core/embed/bootloader_ci/ChangeLog new file mode 100644 index 000000000..f15f7f68f --- /dev/null +++ b/core/embed/bootloader_ci/ChangeLog @@ -0,0 +1,26 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## [Unreleased] + +### Added + +### Deprecated + +### Removed + +### Fixed + +### Security + + +------------ + +### Older changelog: + +Version 1.0.0 [Aug 2020] +* initial version + diff --git a/core/embed/bootloader_ci/README.md b/core/embed/bootloader_ci/README.md new file mode 100644 index 000000000..fd0714ddd --- /dev/null +++ b/core/embed/bootloader_ci/README.md @@ -0,0 +1,16 @@ +# Bootloader for CI device tests + +This bootloader always runs into bootloader mode, waits for firmware to be +uploaded, then runs the firmware. + +Storage is not erased. If you wish to erase storage, do it inside your test. + +All user interaction is removed (no clicking or confirmations required) +so that it can be used in an automated way for tests. + +The bootloader will run any firmware that looks "sane" (good header, hashes), +but does not check any trust flags. + +Firmware must be compiled with `PRODUCTION=0` because otherwise it would +replace the bootloader and lock device. + diff --git a/core/embed/bootloader_ci/bootloader_flash.jlink b/core/embed/bootloader_ci/bootloader_flash.jlink new file mode 100644 index 000000000..c65f37e7d --- /dev/null +++ b/core/embed/bootloader_ci/bootloader_flash.jlink @@ -0,0 +1,7 @@ +device stm32f427vi +if swd +speed auto +loadbin build/bootloader_ci/bootloader.bin 0x08020000 +r +g +exit diff --git a/core/embed/bootloader_ci/bootui.c b/core/embed/bootloader_ci/bootui.c new file mode 120000 index 000000000..8255dcb62 --- /dev/null +++ b/core/embed/bootloader_ci/bootui.c @@ -0,0 +1 @@ +../bootloader/bootui.c \ No newline at end of file diff --git a/core/embed/bootloader_ci/bootui.h b/core/embed/bootloader_ci/bootui.h new file mode 120000 index 000000000..9baee876a --- /dev/null +++ b/core/embed/bootloader_ci/bootui.h @@ -0,0 +1 @@ +../bootloader/bootui.h \ No newline at end of file diff --git a/core/embed/bootloader_ci/header.S b/core/embed/bootloader_ci/header.S new file mode 120000 index 000000000..ae273718d --- /dev/null +++ b/core/embed/bootloader_ci/header.S @@ -0,0 +1 @@ +../bootloader/header.S \ No newline at end of file diff --git a/core/embed/bootloader_ci/icon_cancel.h b/core/embed/bootloader_ci/icon_cancel.h new file mode 120000 index 000000000..21f13d159 --- /dev/null +++ b/core/embed/bootloader_ci/icon_cancel.h @@ -0,0 +1 @@ +../bootloader/icon_cancel.h \ No newline at end of file diff --git a/core/embed/bootloader_ci/icon_confirm.h b/core/embed/bootloader_ci/icon_confirm.h new file mode 120000 index 000000000..cd4d7024f --- /dev/null +++ b/core/embed/bootloader_ci/icon_confirm.h @@ -0,0 +1 @@ +../bootloader/icon_confirm.h \ No newline at end of file diff --git a/core/embed/bootloader_ci/icon_done.h b/core/embed/bootloader_ci/icon_done.h new file mode 120000 index 000000000..30d8d7c83 --- /dev/null +++ b/core/embed/bootloader_ci/icon_done.h @@ -0,0 +1 @@ +../bootloader/icon_done.h \ No newline at end of file diff --git a/core/embed/bootloader_ci/icon_fail.h b/core/embed/bootloader_ci/icon_fail.h new file mode 120000 index 000000000..e521c613f --- /dev/null +++ b/core/embed/bootloader_ci/icon_fail.h @@ -0,0 +1 @@ +../bootloader/icon_fail.h \ No newline at end of file diff --git a/core/embed/bootloader_ci/icon_info.h b/core/embed/bootloader_ci/icon_info.h new file mode 120000 index 000000000..b24400bcb --- /dev/null +++ b/core/embed/bootloader_ci/icon_info.h @@ -0,0 +1 @@ +../bootloader/icon_info.h \ No newline at end of file diff --git a/core/embed/bootloader_ci/icon_install.h b/core/embed/bootloader_ci/icon_install.h new file mode 120000 index 000000000..615ffc096 --- /dev/null +++ b/core/embed/bootloader_ci/icon_install.h @@ -0,0 +1 @@ +../bootloader/icon_install.h \ No newline at end of file diff --git a/core/embed/bootloader_ci/icon_logo.h b/core/embed/bootloader_ci/icon_logo.h new file mode 120000 index 000000000..39123ea9a --- /dev/null +++ b/core/embed/bootloader_ci/icon_logo.h @@ -0,0 +1 @@ +../bootloader/icon_logo.h \ No newline at end of file diff --git a/core/embed/bootloader_ci/icon_safeplace.h b/core/embed/bootloader_ci/icon_safeplace.h new file mode 120000 index 000000000..04fdde31b --- /dev/null +++ b/core/embed/bootloader_ci/icon_safeplace.h @@ -0,0 +1 @@ +../bootloader/icon_safeplace.h \ No newline at end of file diff --git a/core/embed/bootloader_ci/icon_welcome.h b/core/embed/bootloader_ci/icon_welcome.h new file mode 120000 index 000000000..1420701e8 --- /dev/null +++ b/core/embed/bootloader_ci/icon_welcome.h @@ -0,0 +1 @@ +../bootloader/icon_welcome.h \ No newline at end of file diff --git a/core/embed/bootloader_ci/icon_wipe.h b/core/embed/bootloader_ci/icon_wipe.h new file mode 120000 index 000000000..5e7054d17 --- /dev/null +++ b/core/embed/bootloader_ci/icon_wipe.h @@ -0,0 +1 @@ +../bootloader/icon_wipe.h \ No newline at end of file diff --git a/core/embed/bootloader_ci/main.c b/core/embed/bootloader_ci/main.c new file mode 100644 index 000000000..4a37ace1f --- /dev/null +++ b/core/embed/bootloader_ci/main.c @@ -0,0 +1,308 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "common.h" +#include "display.h" +#include "flash.h" +#include "image.h" +#include "mini_printf.h" +#include "mpu.h" +#include "rng.h" +#include "secbool.h" +#include "touch.h" +#include "usb.h" +#include "version.h" + +#include "bootui.h" +#include "messages.h" +// #include "mpu.h" + +const uint8_t BOOTLOADER_KEY_M = 2; +const uint8_t BOOTLOADER_KEY_N = 3; +static const uint8_t * const BOOTLOADER_KEYS[] = { + (const uint8_t *)"\xc2\xc8\x7a\x49\xc5\xa3\x46\x09\x77\xfb\xb2\xec\x9d\xfe\x60\xf0\x6b\xd6\x94\xdb\x82\x44\xbd\x49\x81\xfe\x3b\x7a\x26\x30\x7f\x3f", + (const uint8_t *)"\x80\xd0\x36\xb0\x87\x39\xb8\x46\xf4\xcb\x77\x59\x30\x78\xde\xb2\x5d\xc9\x48\x7a\xed\xcf\x52\xe3\x0b\x4f\xb7\xcd\x70\x24\x17\x8a", + (const uint8_t *)"\xb8\x30\x7a\x71\xf5\x52\xc6\x0a\x4c\xbb\x31\x7f\xf4\x8b\x82\xcd\xbf\x6b\x6b\xb5\xf0\x4c\x92\x0f\xec\x7b\xad\xf0\x17\x88\x37\x51", +// comment the lines above and uncomment the lines below to use a custom signed vendorheader +// (const uint8_t *)"\xd7\x59\x79\x3b\xbc\x13\xa2\x81\x9a\x82\x7c\x76\xad\xb6\xfb\xa8\xa4\x9a\xee\x00\x7f\x49\xf2\xd0\x99\x2d\x99\xb8\x25\xad\x2c\x48", +// (const uint8_t *)"\x63\x55\x69\x1c\x17\x8a\x8f\xf9\x10\x07\xa7\x47\x8a\xfb\x95\x5e\xf7\x35\x2c\x63\xe7\xb2\x57\x03\x98\x4c\xf7\x8b\x26\xe2\x1a\x56", +// (const uint8_t *)"\xee\x93\xa4\xf6\x6f\x8d\x16\xb8\x19\xbb\x9b\xeb\x9f\xfc\xcd\xfc\xdc\x14\x12\xe8\x7f\xee\x6a\x32\x4c\x2a\x99\xa1\xe0\xe6\x71\x48", +}; + +#define USB_IFACE_NUM 0 + +static void usb_init_all(secbool usb21_landing) { + usb_dev_info_t dev_info = { + .device_class = 0x00, + .device_subclass = 0x00, + .device_protocol = 0x00, + .vendor_id = 0x1209, + .product_id = 0x53C0, + .release_num = 0x0200, + .manufacturer = "SatoshiLabs", + .product = "TREZOR", + .serial_number = "000000000000000000000000", + .interface = "TREZOR Interface", + .usb21_enabled = sectrue, + .usb21_landing = usb21_landing, + }; + + static uint8_t rx_buffer[USB_PACKET_SIZE]; + + static const usb_webusb_info_t webusb_info = { + .iface_num = USB_IFACE_NUM, + .ep_in = USB_EP_DIR_IN | 0x01, + .ep_out = USB_EP_DIR_OUT | 0x01, + .subclass = 0, + .protocol = 0, + .max_packet_len = sizeof(rx_buffer), + .rx_buffer = rx_buffer, + .polling_interval = 1, + }; + + usb_init(&dev_info); + + ensure(usb_webusb_add(&webusb_info), NULL); + + usb_start(); +} + +static secbool bootloader_usb_loop(const vendor_header *const vhdr, + const image_header *const hdr) { + // if both are NULL, we don't have a firmware installed + // let's show a webusb landing page in this case + usb_init_all((vhdr == NULL && hdr == NULL) ? sectrue : secfalse); + + uint8_t buf[USB_PACKET_SIZE]; + + for (;;) { + int r = usb_webusb_read_blocking(USB_IFACE_NUM, buf, USB_PACKET_SIZE, + USB_TIMEOUT); + if (r != USB_PACKET_SIZE) { + continue; + } + uint16_t msg_id; + uint32_t msg_size; + if (sectrue != msg_parse_header(buf, &msg_id, &msg_size)) { + // invalid header -> discard + continue; + } + switch (msg_id) { + case 0: // Initialize + process_msg_Initialize(USB_IFACE_NUM, msg_size, buf, vhdr, hdr); + break; + case 1: // Ping + process_msg_Ping(USB_IFACE_NUM, msg_size, buf); + break; + case 5: // WipeDevice + ui_fadeout(); + ui_screen_wipe_confirm(); + ui_fadein(); + int response = ui_user_input(INPUT_CONFIRM | INPUT_CANCEL); + if (INPUT_CANCEL == response) { + ui_fadeout(); + ui_screen_info(secfalse, vhdr, hdr); + ui_fadein(); + send_user_abort(USB_IFACE_NUM, "Wipe cancelled"); + break; + } + ui_fadeout(); + ui_screen_wipe(); + ui_fadein(); + r = process_msg_WipeDevice(USB_IFACE_NUM, msg_size, buf); + if (r < 0) { // error + ui_fadeout(); + ui_screen_fail(); + ui_fadein(); + usb_stop(); + usb_deinit(); + return secfalse; // shutdown + } else { // success + ui_fadeout(); + ui_screen_done(0, sectrue); + ui_fadein(); + usb_stop(); + usb_deinit(); + return secfalse; // shutdown + } + break; + case 6: // FirmwareErase + process_msg_FirmwareErase(USB_IFACE_NUM, msg_size, buf); + break; + case 7: // FirmwareUpload + r = process_msg_FirmwareUpload(USB_IFACE_NUM, msg_size, buf); + if (r < 0 && r != -4) { // error, but not user abort (-4) + ui_fadeout(); + ui_screen_fail(); + ui_fadein(); + usb_stop(); + usb_deinit(); + return secfalse; // shutdown + } else if (r == 0) { // last chunk received + ui_screen_install_progress_upload(1000); + ui_fadeout(); + ui_screen_done(4, sectrue); + ui_fadein(); + ui_screen_done(3, secfalse); + hal_delay(1000); + ui_screen_done(2, secfalse); + hal_delay(1000); + ui_screen_done(1, secfalse); + hal_delay(1000); + usb_stop(); + usb_deinit(); + ui_fadeout(); + return sectrue; // jump to firmware + } + break; + case 55: // GetFeatures + process_msg_GetFeatures(USB_IFACE_NUM, msg_size, buf, vhdr, hdr); + break; + default: + process_msg_unknown(USB_IFACE_NUM, msg_size, buf); + break; + } + } +} + +secbool load_vendor_header_keys(const uint8_t *const data, + vendor_header *const vhdr) { + return load_vendor_header(data, BOOTLOADER_KEY_M, BOOTLOADER_KEY_N, + BOOTLOADER_KEYS, vhdr); +} + +static secbool check_vendor_keys_lock(const vendor_header *const vhdr) { + uint8_t lock[FLASH_OTP_BLOCK_SIZE]; + ensure(flash_otp_read(FLASH_OTP_BLOCK_VENDOR_KEYS_LOCK, 0, lock, + FLASH_OTP_BLOCK_SIZE), + NULL); + if (0 == + memcmp(lock, + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", + FLASH_OTP_BLOCK_SIZE)) { + return sectrue; + } + uint8_t hash[32]; + vendor_keys_hash(vhdr, hash); + return sectrue * (0 == memcmp(lock, hash, 32)); +} + +// protection against bootloader downgrade + +#if PRODUCTION + +static void check_bootloader_version(void) { + uint8_t bits[FLASH_OTP_BLOCK_SIZE]; + for (int i = 0; i < FLASH_OTP_BLOCK_SIZE * 8; i++) { + if (i < VERSION_MONOTONIC) { + bits[i / 8] &= ~(1 << (7 - (i % 8))); + } else { + bits[i / 8] |= (1 << (7 - (i % 8))); + } + } + ensure(flash_otp_write(FLASH_OTP_BLOCK_BOOTLOADER_VERSION, 0, bits, + FLASH_OTP_BLOCK_SIZE), + NULL); + + uint8_t bits2[FLASH_OTP_BLOCK_SIZE]; + ensure(flash_otp_read(FLASH_OTP_BLOCK_BOOTLOADER_VERSION, 0, bits2, + FLASH_OTP_BLOCK_SIZE), + NULL); + + ensure(sectrue * (0 == memcmp(bits, bits2, FLASH_OTP_BLOCK_SIZE)), + "Bootloader downgraded"); +} + +#endif + +int main(void) { + drbg_init(); + touch_init(); + touch_power_on(); + + mpu_config_bootloader(); + +#if PRODUCTION + check_bootloader_version(); +#endif + + display_clear(); + + vendor_header vhdr; + image_header hdr; + secbool firmware_present; + + // detect whether the devices contains a valid firmware + + firmware_present = + load_vendor_header_keys((const uint8_t *)FIRMWARE_START, &vhdr); + if (sectrue == firmware_present) { + firmware_present = check_vendor_keys_lock(&vhdr); + } + if (sectrue == firmware_present) { + 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); + } + + // always start bootloader even if firmware is already present + // show intro animation + + // no ui_fadeout(); - we already start from black screen + ui_screen_third(); + ui_fadein(); + + // and start the usb loop + if (bootloader_usb_loop(NULL, NULL) != sectrue) { + return 1; + } + + ensure(load_vendor_header_keys((const uint8_t *)FIRMWARE_START, &vhdr), + "invalid vendor header"); + + ensure(check_vendor_keys_lock(&vhdr), "unauthorized vendor keys"); + + ensure(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), + "invalid firmware header"); + + ensure(check_image_contents(&hdr, IMAGE_HEADER_SIZE + vhdr.hdrlen, + FIRMWARE_SECTORS, FIRMWARE_SECTORS_COUNT), + "invalid firmware hash"); + + // do not check any trust flags on header, proceed + + // 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/core/embed/bootloader_ci/memory.ld b/core/embed/bootloader_ci/memory.ld new file mode 120000 index 000000000..81c15226b --- /dev/null +++ b/core/embed/bootloader_ci/memory.ld @@ -0,0 +1 @@ +../bootloader/memory.ld \ No newline at end of file diff --git a/core/embed/bootloader_ci/messages.c b/core/embed/bootloader_ci/messages.c new file mode 100644 index 000000000..eddb6d66d --- /dev/null +++ b/core/embed/bootloader_ci/messages.c @@ -0,0 +1,654 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include +#include +#include +#include "messages.pb.h" + +#include "common.h" +#include "flash.h" +#include "image.h" +#include "secbool.h" +#include "usb.h" +#include "version.h" + +#include "bootui.h" +#include "messages.h" + +#include "memzero.h" + +#define MSG_HEADER1_LEN 9 +#define MSG_HEADER2_LEN 1 + +secbool msg_parse_header(const uint8_t *buf, uint16_t *msg_id, + uint32_t *msg_size) { + if (buf[0] != '?' || buf[1] != '#' || buf[2] != '#') { + return secfalse; + } + *msg_id = (buf[3] << 8) + buf[4]; + *msg_size = (buf[5] << 24) + (buf[6] << 16) + (buf[7] << 8) + buf[8]; + return sectrue; +} + +typedef struct { + uint8_t iface_num; + uint8_t packet_index; + uint8_t packet_pos; + uint8_t buf[USB_PACKET_SIZE]; +} usb_write_state; + +/* we don't use secbool/sectrue/secfalse here as it is a nanopb api */ +static bool _usb_write(pb_ostream_t *stream, const pb_byte_t *buf, + size_t count) { + usb_write_state *state = (usb_write_state *)(stream->state); + + size_t written = 0; + // while we have data left + while (written < count) { + size_t remaining = count - written; + // if all remaining data fit into our packet + if (state->packet_pos + remaining <= USB_PACKET_SIZE) { + // append data from buf to state->buf + memcpy(state->buf + state->packet_pos, buf + written, remaining); + // advance position + state->packet_pos += remaining; + // and return + return true; + } else { + // append data that fits + memcpy(state->buf + state->packet_pos, buf + written, + USB_PACKET_SIZE - state->packet_pos); + written += USB_PACKET_SIZE - state->packet_pos; + // send packet + int r = usb_webusb_write_blocking(state->iface_num, state->buf, + USB_PACKET_SIZE, USB_TIMEOUT); + ensure(sectrue * (r == USB_PACKET_SIZE), NULL); + // prepare new packet + state->packet_index++; + memzero(state->buf, USB_PACKET_SIZE); + state->buf[0] = '?'; + state->packet_pos = MSG_HEADER2_LEN; + } + } + + return true; +} + +static void _usb_write_flush(usb_write_state *state) { + // if packet is not filled up completely + if (state->packet_pos < USB_PACKET_SIZE) { + // pad it with zeroes + memzero(state->buf + state->packet_pos, + USB_PACKET_SIZE - state->packet_pos); + } + // send packet + int r = usb_webusb_write_blocking(state->iface_num, state->buf, + USB_PACKET_SIZE, USB_TIMEOUT); + ensure(sectrue * (r == USB_PACKET_SIZE), NULL); +} + +static secbool _send_msg(uint8_t iface_num, uint16_t msg_id, + const pb_msgdesc_t *fields, const void *msg) { + // determine message size by serializing it into a dummy stream + pb_ostream_t sizestream = {.callback = NULL, + .state = NULL, + .max_size = SIZE_MAX, + .bytes_written = 0, + .errmsg = NULL}; + if (false == pb_encode(&sizestream, fields, msg)) { + return secfalse; + } + const uint32_t msg_size = sizestream.bytes_written; + + usb_write_state state = { + .iface_num = iface_num, + .packet_index = 0, + .packet_pos = MSG_HEADER1_LEN, + .buf = + { + '?', + '#', + '#', + (msg_id >> 8) & 0xFF, + msg_id & 0xFF, + (msg_size >> 24) & 0xFF, + (msg_size >> 16) & 0xFF, + (msg_size >> 8) & 0xFF, + msg_size & 0xFF, + }, + }; + + pb_ostream_t stream = {.callback = &_usb_write, + .state = &state, + .max_size = SIZE_MAX, + .bytes_written = 0, + .errmsg = NULL}; + + if (false == pb_encode(&stream, fields, msg)) { + return secfalse; + } + + _usb_write_flush(&state); + + return sectrue; +} + +#define MSG_SEND_INIT(TYPE) TYPE msg_send = TYPE##_init_default +#define MSG_SEND_ASSIGN_VALUE(FIELD, VALUE) \ + { \ + msg_send.has_##FIELD = true; \ + msg_send.FIELD = VALUE; \ + } +#define MSG_SEND_ASSIGN_STRING(FIELD, VALUE) \ + { \ + msg_send.has_##FIELD = true; \ + memzero(msg_send.FIELD, sizeof(msg_send.FIELD)); \ + strncpy(msg_send.FIELD, VALUE, sizeof(msg_send.FIELD) - 1); \ + } +#define MSG_SEND_ASSIGN_STRING_LEN(FIELD, VALUE, LEN) \ + { \ + msg_send.has_##FIELD = true; \ + memzero(msg_send.FIELD, sizeof(msg_send.FIELD)); \ + strncpy(msg_send.FIELD, VALUE, MIN(LEN, sizeof(msg_send.FIELD) - 1)); \ + } +#define MSG_SEND_ASSIGN_BYTES(FIELD, VALUE, LEN) \ + { \ + msg_send.has_##FIELD = true; \ + memzero(msg_send.FIELD.bytes, sizeof(msg_send.FIELD.bytes)); \ + memcpy(msg_send.FIELD.bytes, VALUE, \ + MIN(LEN, sizeof(msg_send.FIELD.bytes))); \ + msg_send.FIELD.size = MIN(LEN, sizeof(msg_send.FIELD.bytes)); \ + } +#define MSG_SEND(TYPE) \ + _send_msg(iface_num, MessageType_MessageType_##TYPE, TYPE##_fields, &msg_send) + +typedef struct { + uint8_t iface_num; + uint8_t packet_index; + uint8_t packet_pos; + uint8_t *buf; +} usb_read_state; + +static void _usb_webusb_read_retry(uint8_t iface_num, uint8_t *buf) { + for (int retry = 0;; retry++) { + int r = + usb_webusb_read_blocking(iface_num, buf, USB_PACKET_SIZE, USB_TIMEOUT); + if (r != USB_PACKET_SIZE) { // reading failed + if (r == 0 && retry < 10) { + // only timeout => let's try again + } else { + // error + error_shutdown("Error reading", "from USB.", "Try different", + "USB cable."); + } + } + return; // success + } +} + +/* we don't use secbool/sectrue/secfalse here as it is a nanopb api */ +static bool _usb_read(pb_istream_t *stream, uint8_t *buf, size_t count) { + usb_read_state *state = (usb_read_state *)(stream->state); + + size_t read = 0; + // while we have data left + while (read < count) { + size_t remaining = count - read; + // if all remaining data fit into our packet + if (state->packet_pos + remaining <= USB_PACKET_SIZE) { + // append data from buf to state->buf + memcpy(buf + read, state->buf + state->packet_pos, remaining); + // advance position + state->packet_pos += remaining; + // and return + return true; + } else { + // append data that fits + memcpy(buf + read, state->buf + state->packet_pos, + USB_PACKET_SIZE - state->packet_pos); + read += USB_PACKET_SIZE - state->packet_pos; + // read next packet (with retry) + _usb_webusb_read_retry(state->iface_num, state->buf); + // prepare next packet + state->packet_index++; + state->packet_pos = MSG_HEADER2_LEN; + } + } + + return true; +} + +static void _usb_read_flush(usb_read_state *state) { (void)state; } + +static secbool _recv_msg(uint8_t iface_num, uint32_t msg_size, uint8_t *buf, + const pb_msgdesc_t *fields, void *msg) { + usb_read_state state = {.iface_num = iface_num, + .packet_index = 0, + .packet_pos = MSG_HEADER1_LEN, + .buf = buf}; + + pb_istream_t stream = {.callback = &_usb_read, + .state = &state, + .bytes_left = msg_size, + .errmsg = NULL}; + + if (false == pb_decode_noinit(&stream, fields, msg)) { + return secfalse; + } + + _usb_read_flush(&state); + + return sectrue; +} + +#define MSG_RECV_INIT(TYPE) TYPE msg_recv = TYPE##_init_default +#define MSG_RECV_CALLBACK(FIELD, CALLBACK, ARGUMENT) \ + { \ + msg_recv.FIELD.funcs.decode = &CALLBACK; \ + msg_recv.FIELD.arg = (void *)ARGUMENT; \ + } +#define MSG_RECV(TYPE) \ + _recv_msg(iface_num, msg_size, buf, TYPE##_fields, &msg_recv) + +void send_user_abort(uint8_t iface_num, const char *msg) { + MSG_SEND_INIT(Failure); + MSG_SEND_ASSIGN_VALUE(code, FailureType_Failure_ActionCancelled); + MSG_SEND_ASSIGN_STRING(message, msg); + MSG_SEND(Failure); +} + +static void send_msg_features(uint8_t iface_num, + const vendor_header *const vhdr, + const image_header *const hdr) { + MSG_SEND_INIT(Features); + MSG_SEND_ASSIGN_STRING(vendor, "trezor.io"); + MSG_SEND_ASSIGN_VALUE(major_version, VERSION_MAJOR); + MSG_SEND_ASSIGN_VALUE(minor_version, VERSION_MINOR); + MSG_SEND_ASSIGN_VALUE(patch_version, VERSION_PATCH); + MSG_SEND_ASSIGN_VALUE(bootloader_mode, true); + MSG_SEND_ASSIGN_STRING(model, "T"); + if (vhdr && hdr) { + MSG_SEND_ASSIGN_VALUE(firmware_present, true); + MSG_SEND_ASSIGN_VALUE(fw_major, (hdr->version & 0xFF)); + MSG_SEND_ASSIGN_VALUE(fw_minor, ((hdr->version >> 8) & 0xFF)); + MSG_SEND_ASSIGN_VALUE(fw_patch, ((hdr->version >> 16) & 0xFF)); + MSG_SEND_ASSIGN_STRING_LEN(fw_vendor, vhdr->vstr, vhdr->vstr_len); + uint8_t hash[32]; + vendor_keys_hash(vhdr, hash); + MSG_SEND_ASSIGN_BYTES(fw_vendor_keys, hash, 32); + } else { + MSG_SEND_ASSIGN_VALUE(firmware_present, false); + } + MSG_SEND(Features); +} + +void process_msg_Initialize(uint8_t iface_num, uint32_t msg_size, uint8_t *buf, + const vendor_header *const vhdr, + const image_header *const hdr) { + MSG_RECV_INIT(Initialize); + MSG_RECV(Initialize); + send_msg_features(iface_num, vhdr, hdr); +} + +void process_msg_GetFeatures(uint8_t iface_num, uint32_t msg_size, uint8_t *buf, + const vendor_header *const vhdr, + const image_header *const hdr) { + MSG_RECV_INIT(GetFeatures); + MSG_RECV(GetFeatures); + send_msg_features(iface_num, vhdr, hdr); +} + +void process_msg_Ping(uint8_t iface_num, uint32_t msg_size, uint8_t *buf) { + MSG_RECV_INIT(Ping); + MSG_RECV(Ping); + + MSG_SEND_INIT(Success); + MSG_SEND_ASSIGN_STRING(message, msg_recv.message); + MSG_SEND(Success); +} + +static uint32_t firmware_remaining, firmware_block, chunk_requested; + +void process_msg_FirmwareErase(uint8_t iface_num, uint32_t msg_size, + uint8_t *buf) { + firmware_remaining = 0; + firmware_block = 0; + chunk_requested = 0; + + MSG_RECV_INIT(FirmwareErase); + MSG_RECV(FirmwareErase); + + firmware_remaining = msg_recv.has_length ? msg_recv.length : 0; + if ((firmware_remaining > 0) && + ((firmware_remaining % sizeof(uint32_t)) == 0) && + (firmware_remaining <= (FIRMWARE_SECTORS_COUNT * IMAGE_CHUNK_SIZE))) { + // request new firmware + chunk_requested = (firmware_remaining > IMAGE_INIT_CHUNK_SIZE) + ? IMAGE_INIT_CHUNK_SIZE + : firmware_remaining; + MSG_SEND_INIT(FirmwareRequest); + MSG_SEND_ASSIGN_VALUE(offset, 0); + MSG_SEND_ASSIGN_VALUE(length, chunk_requested); + MSG_SEND(FirmwareRequest); + } else { + // invalid firmware size + MSG_SEND_INIT(Failure); + MSG_SEND_ASSIGN_VALUE(code, FailureType_Failure_DataError); + MSG_SEND_ASSIGN_STRING(message, "Wrong firmware size"); + MSG_SEND(Failure); + } +} + +static uint32_t chunk_size = 0; +// SRAM is unused, so we can use it for chunk buffer +uint8_t *const chunk_buffer = (uint8_t *const)0x20000000; + +/* we don't use secbool/sectrue/secfalse here as it is a nanopb api */ +static bool _read_payload(pb_istream_t *stream, const pb_field_t *field, + void **arg) { +#define BUFSIZE 32768 + + uint32_t offset = (uint32_t)(*arg); + + if (stream->bytes_left > IMAGE_CHUNK_SIZE) { + chunk_size = 0; + return false; + } + + if (offset == 0) { + // clear chunk buffer + memset(chunk_buffer, 0xFF, IMAGE_CHUNK_SIZE); + } + + uint32_t chunk_written = offset; + chunk_size = offset + stream->bytes_left; + + while (stream->bytes_left) { + // update loader but skip first block + if (firmware_block > 0) { + ui_screen_install_progress_upload( + 250 + 750 * (firmware_block * IMAGE_CHUNK_SIZE + chunk_written) / + (firmware_block * IMAGE_CHUNK_SIZE + firmware_remaining)); + } + // read data + if (!pb_read( + stream, (pb_byte_t *)(chunk_buffer + chunk_written), + (stream->bytes_left > BUFSIZE) ? BUFSIZE : stream->bytes_left)) { + chunk_size = 0; + return false; + } + chunk_written += BUFSIZE; + } + + return true; +} + +secbool load_vendor_header_keys(const uint8_t *const data, + vendor_header *const vhdr); + +static int version_compare(uint32_t vera, uint32_t verb) { + int a, b; + a = vera & 0xFF; + b = verb & 0xFF; + if (a != b) return a - b; + a = (vera >> 8) & 0xFF; + b = (verb >> 8) & 0xFF; + if (a != b) return a - b; + a = (vera >> 16) & 0xFF; + b = (verb >> 16) & 0xFF; + if (a != b) return a - b; + a = (vera >> 24) & 0xFF; + b = (verb >> 24) & 0xFF; + return a - b; +} + +static void detect_installation(vendor_header *current_vhdr, + image_header *current_hdr, + const vendor_header *const new_vhdr, + const image_header *const new_hdr, + secbool *is_new, secbool *is_upgrade, + secbool *is_downgrade_wipe) { + *is_new = secfalse; + *is_upgrade = secfalse; + *is_downgrade_wipe = secfalse; + if (sectrue != + load_vendor_header_keys((const uint8_t *)FIRMWARE_START, current_vhdr)) { + *is_new = sectrue; + return; + } + if (sectrue != + load_image_header((const uint8_t *)FIRMWARE_START + current_vhdr->hdrlen, + FIRMWARE_IMAGE_MAGIC, FIRMWARE_IMAGE_MAXSIZE, + current_vhdr->vsig_m, current_vhdr->vsig_n, + current_vhdr->vpub, current_hdr)) { + *is_new = sectrue; + return; + } + uint8_t hash1[32], hash2[32]; + vendor_keys_hash(new_vhdr, hash1); + vendor_keys_hash(current_vhdr, hash2); + if (0 != memcmp(hash1, hash2, 32)) { + return; + } + if (version_compare(new_hdr->version, current_hdr->fix_version) < 0) { + *is_downgrade_wipe = sectrue; + return; + } + *is_upgrade = sectrue; +} + +static int firmware_upload_chunk_retry = FIRMWARE_UPLOAD_CHUNK_RETRY_COUNT; +static uint32_t headers_offset = 0; +static uint32_t read_offset = 0; + +int process_msg_FirmwareUpload(uint8_t iface_num, uint32_t msg_size, + uint8_t *buf) { + MSG_RECV_INIT(FirmwareUpload); + MSG_RECV_CALLBACK(payload, _read_payload, read_offset); + secbool r = MSG_RECV(FirmwareUpload); + + if (sectrue != r || chunk_size != (chunk_requested + read_offset)) { + MSG_SEND_INIT(Failure); + MSG_SEND_ASSIGN_VALUE(code, FailureType_Failure_DataError); + MSG_SEND_ASSIGN_STRING(message, "Invalid chunk size"); + MSG_SEND(Failure); + return -1; + } + + static image_header hdr; + secbool is_upgrade = secfalse; + secbool is_downgrade_wipe = secfalse; + + if (firmware_block == 0) { + if (headers_offset == 0) { + // first block and headers are not yet parsed + vendor_header vhdr; + if (sectrue != load_vendor_header_keys(chunk_buffer, &vhdr)) { + MSG_SEND_INIT(Failure); + MSG_SEND_ASSIGN_VALUE(code, FailureType_Failure_ProcessError); + MSG_SEND_ASSIGN_STRING(message, "Invalid vendor header"); + MSG_SEND(Failure); + return -2; + } + if (sectrue != load_image_header(chunk_buffer + vhdr.hdrlen, + FIRMWARE_IMAGE_MAGIC, + FIRMWARE_IMAGE_MAXSIZE, vhdr.vsig_m, + vhdr.vsig_n, vhdr.vpub, &hdr)) { + MSG_SEND_INIT(Failure); + MSG_SEND_ASSIGN_VALUE(code, FailureType_Failure_ProcessError); + MSG_SEND_ASSIGN_STRING(message, "Invalid firmware header"); + MSG_SEND(Failure); + return -3; + } + + vendor_header current_vhdr; + image_header current_hdr; + secbool is_new = secfalse; + detect_installation(¤t_vhdr, ¤t_hdr, &vhdr, &hdr, &is_new, + &is_upgrade, &is_downgrade_wipe); + + // no user confirmations, go directly to upload + + headers_offset = IMAGE_HEADER_SIZE + vhdr.hdrlen; + read_offset = IMAGE_INIT_CHUNK_SIZE; + + // request the rest of the first chunk + MSG_SEND_INIT(FirmwareRequest); + chunk_requested = IMAGE_CHUNK_SIZE - read_offset; + MSG_SEND_ASSIGN_VALUE(offset, read_offset); + MSG_SEND_ASSIGN_VALUE(length, chunk_requested); + MSG_SEND(FirmwareRequest); + + firmware_remaining -= read_offset; + return (int)firmware_remaining; + } else { + // first block with the headers parsed -> the first chunk is now complete + read_offset = 0; + + ui_fadeout(); + ui_screen_install(); + ui_fadein(); + + ensure(flash_erase_sectors(FIRMWARE_SECTORS, FIRMWARE_SECTORS_COUNT, + ui_screen_install_progress_erase), + NULL); + } + } + + // should not happen, but double-check + if (firmware_block >= FIRMWARE_SECTORS_COUNT) { + MSG_SEND_INIT(Failure); + MSG_SEND_ASSIGN_VALUE(code, FailureType_Failure_ProcessError); + MSG_SEND_ASSIGN_STRING(message, "Firmware too big"); + MSG_SEND(Failure); + return -5; + } + + if (sectrue != check_single_hash(hdr.hashes + firmware_block * 32, + chunk_buffer + headers_offset, + chunk_size - headers_offset)) { + if (firmware_upload_chunk_retry > 0) { + --firmware_upload_chunk_retry; + MSG_SEND_INIT(FirmwareRequest); + MSG_SEND_ASSIGN_VALUE(offset, firmware_block * IMAGE_CHUNK_SIZE); + MSG_SEND_ASSIGN_VALUE(length, chunk_requested); + MSG_SEND(FirmwareRequest); + return (int)firmware_remaining; + } + + MSG_SEND_INIT(Failure); + MSG_SEND_ASSIGN_VALUE(code, FailureType_Failure_ProcessError); + MSG_SEND_ASSIGN_STRING(message, "Invalid chunk hash"); + MSG_SEND(Failure); + return -6; + } + + ensure(flash_unlock_write(), NULL); + + 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_lock_write(), NULL); + + headers_offset = 0; + firmware_remaining -= chunk_requested; + firmware_block++; + firmware_upload_chunk_retry = FIRMWARE_UPLOAD_CHUNK_RETRY_COUNT; + + if (firmware_remaining > 0) { + chunk_requested = (firmware_remaining > IMAGE_CHUNK_SIZE) + ? IMAGE_CHUNK_SIZE + : firmware_remaining; + MSG_SEND_INIT(FirmwareRequest); + MSG_SEND_ASSIGN_VALUE(offset, firmware_block * IMAGE_CHUNK_SIZE); + MSG_SEND_ASSIGN_VALUE(length, chunk_requested); + MSG_SEND(FirmwareRequest); + } else { + MSG_SEND_INIT(Success); + MSG_SEND(Success); + } + return (int)firmware_remaining; +} + +int process_msg_WipeDevice(uint8_t iface_num, uint32_t msg_size, uint8_t *buf) { + static const uint8_t sectors[] = { + FLASH_SECTOR_STORAGE_1, + FLASH_SECTOR_STORAGE_2, + // 3, // skip because of MPU protection + FLASH_SECTOR_FIRMWARE_START, + 7, + 8, + 9, + 10, + FLASH_SECTOR_FIRMWARE_END, + FLASH_SECTOR_UNUSED_START, + 13, + 14, + // FLASH_SECTOR_UNUSED_END, // skip because of MPU protection + FLASH_SECTOR_FIRMWARE_EXTRA_START, + 18, + 19, + 20, + 21, + 22, + FLASH_SECTOR_FIRMWARE_EXTRA_END, + }; + if (sectrue != + flash_erase_sectors(sectors, sizeof(sectors), ui_screen_wipe_progress)) { + MSG_SEND_INIT(Failure); + MSG_SEND_ASSIGN_VALUE(code, FailureType_Failure_ProcessError); + MSG_SEND_ASSIGN_STRING(message, "Could not erase flash"); + MSG_SEND(Failure); + return -1; + } else { + MSG_SEND_INIT(Success); + MSG_SEND(Success); + return 0; + } +} + +void process_msg_unknown(uint8_t iface_num, uint32_t msg_size, uint8_t *buf) { + // consume remaining message + int remaining_chunks = 0; + + if (msg_size > (USB_PACKET_SIZE - MSG_HEADER1_LEN)) { + // calculate how many blocks need to be read to drain the message (rounded + // up to not leave any behind) + remaining_chunks = (msg_size - (USB_PACKET_SIZE - MSG_HEADER1_LEN) + + ((USB_PACKET_SIZE - MSG_HEADER2_LEN) - 1)) / + (USB_PACKET_SIZE - MSG_HEADER2_LEN); + } + + for (int i = 0; i < remaining_chunks; i++) { + // read next packet (with retry) + _usb_webusb_read_retry(iface_num, buf); + } + + MSG_SEND_INIT(Failure); + MSG_SEND_ASSIGN_VALUE(code, FailureType_Failure_UnexpectedMessage); + MSG_SEND_ASSIGN_STRING(message, "Unexpected message"); + MSG_SEND(Failure); +} diff --git a/core/embed/bootloader_ci/messages.h b/core/embed/bootloader_ci/messages.h new file mode 120000 index 000000000..439bd22f2 --- /dev/null +++ b/core/embed/bootloader_ci/messages.h @@ -0,0 +1 @@ +../bootloader/messages.h \ No newline at end of file diff --git a/core/embed/bootloader_ci/protob b/core/embed/bootloader_ci/protob new file mode 120000 index 000000000..40c06dbe5 --- /dev/null +++ b/core/embed/bootloader_ci/protob @@ -0,0 +1 @@ +../bootloader/protob \ No newline at end of file diff --git a/core/embed/bootloader_ci/startup.s b/core/embed/bootloader_ci/startup.s new file mode 120000 index 000000000..7b784274a --- /dev/null +++ b/core/embed/bootloader_ci/startup.s @@ -0,0 +1 @@ +../bootloader/startup.s \ No newline at end of file diff --git a/core/embed/bootloader_ci/version.h b/core/embed/bootloader_ci/version.h new file mode 120000 index 000000000..0caef111b --- /dev/null +++ b/core/embed/bootloader_ci/version.h @@ -0,0 +1 @@ +../bootloader/version.h \ No newline at end of file