refactor(core): make boot_ucb/header implementation hw specific
What changed, and why it matters
This commit is a pure code reorganization: it moves the STM32-specific bootloader header, bootloader image, and update-control block (UCB) code from a shared directory into a hardware-specific 'stm32' subdirectory, and adds a minimal Unix emulator stub for UCB. The actual logic in the moved files is byte-for-byte identical to the original. No security behavior is changed, no bugs are fixed, and no vulnerabilities are introduced based on the supplied diff.
No security action required. Treat as routine refactoring. If reviewing further, verify that downstream build targets and include paths still resolve correctly and that the Unix emulator stub does not accidentally get linked into production STM32 builds.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change refactors core/embed/sec/image/ by relocating boot_header.c, boot_image.c, and boot_ucb.c into core/embed/sec/image/stm32/ and adding core/embed/sec/image/unix/boot_ucb.c. The SConscript files and model build scripts are updated to point to the new paths. A line-by-line comparison of the deleted and added files shows identical content, confirming this is a non-functional move. The new unix/boot_ucb.c only provides a stub boot_ucb_erase() returning sectrue, which is appropriate for emulator builds that lack real flash UCB storage.
Changed components
core/embed/sec/image/stm32/boot_header.ccore/embed/sec/image/stm32/boot_image.ccore/embed/sec/image/stm32/boot_ucb.ccore/embed/sec/image/unix/boot_ucb.ccore/SConscript.kernelcore/SConscript.prodtestcore/SConscript.prodtest_emucore/SConscript.secmoncore/site_scons/models/D002/discovery2.pycore/site_scons/models/T3W1/trezor_t3w1_revA.pycore/site_scons/models/T3W1/trezor_t3w1_revB.pycore/site_scons/models/T3W1/trezor_t3w1_revC.pyInspect captured patch +766 / −744
diff --git a/core/SConscript.kernel b/core/SConscript.kernel
index cb0e64d0..7b931e37 100644
--- a/core/SConscript.kernel
+++ b/core/SConscript.kernel
@@ -225,7 +225,7 @@ SOURCE_MOD += [
'embed/rtl/error_handling.c',
'embed/rtl/scm_revision.c',
'embed/rtl/strutils.c',
- 'embed/sec/image/boot_image.c',
+ 'embed/sec/image/stm32/boot_image.c',
'embed/sec/image/image.c',
'embed/sec/rsod/rsod_special.c',
'vendor/micropython/lib/uzlib/adler32.c',
diff --git a/core/SConscript.prodtest b/core/SConscript.prodtest
index e42179e9..b19db13f 100644
--- a/core/SConscript.prodtest
+++ b/core/SConscript.prodtest
@@ -167,7 +167,7 @@ SOURCE_MOD += [
'embed/rtl/scm_revision.c',
'embed/rtl/strutils.c',
'embed/rtl/unit_test.c',
- 'embed/sec/image/boot_image.c',
+ 'embed/sec/image/stm32/boot_image.c',
'embed/sec/image/image.c',
'embed/sec/rsod/rsod_special.c',
'vendor/micropython/lib/uzlib/adler32.c',
diff --git a/core/SConscript.prodtest_emu b/core/SConscript.prodtest_emu
index d67ab125..c2f13029 100644
--- a/core/SConscript.prodtest_emu
+++ b/core/SConscript.prodtest_emu
@@ -136,7 +136,7 @@ SOURCE_MOD += [
'embed/rtl/scm_revision.c',
'embed/rtl/strutils.c',
'embed/rtl/unit_test.c',
- 'embed/sec/image/boot_image.c',
+ 'embed/sec/image/stm32/boot_image.c',
'embed/sec/image/image.c',
'embed/sec/rsod/rsod_special.c',
'vendor/micropython/lib/uzlib/adler32.c',
diff --git a/core/SConscript.secmon b/core/SConscript.secmon
index 990b0a93..f7c5e8a2 100644
--- a/core/SConscript.secmon
+++ b/core/SConscript.secmon
@@ -220,7 +220,7 @@ SOURCE_MOD += [
'embed/io/translations/translations.c',
'embed/rtl/error_handling.c',
'embed/rtl/strutils.c',
- 'embed/sec/image/boot_image.c',
+ 'embed/sec/image/stm32/boot_image.c',
'embed/sec/image/image.c',
'embed/sec/rsod/rsod_special.c',
'vendor/micropython/lib/uzlib/adler32.c',
diff --git a/core/embed/sec/image/boot_header.c b/core/embed/sec/image/boot_header.c
deleted file mode 100644
index ec2ca636..00000000
--- a/core/embed/sec/image/boot_header.c
+++ /dev/null
@@ -1,275 +0,0 @@
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-#ifdef SECURE_MODE
-
-#include <trezor_model.h>
-#include <trezor_rtl.h>
-
-#include <rtl/sizedefs.h>
-#include <sec/boot_header.h>
-#include <sec/image_hash_conf.h>
-
-#include <../vendor/sphincsplus/ref/api.h>
-#include <ed25519-donna/ed25519.h>
-
-static const uint8_t * const BOARDLOADER_PQ_KEYS[] = {
-#if BOOTLOADER_DEVEL
- (const uint8_t*) "\xec\x01\xe6\x02\x63\x02\x4f\x7e\x71\x72\x80\x13\xb7\x31\xf7\xba\x12\x99\xf5\x18\xc2\x7b\xa3\xed\x8f\x4a\x21\x99\x74\x12\x7c\x62",
- (const uint8_t*) "\x8a\xf8\x87\x80\x85\x94\x6e\xd8\xb1\x16\xbd\x24\xc0\xf2\xaa\xc4\x8b\x7e\x8f\x11\xbf\x06\x87\x25\xcc\xfb\xb1\x52\xab\xf7\xa4\xcd",
-#else
- MODEL_BOARDLOADER_PQ_KEYS
-#endif
-};
-
-static const uint8_t * const BOARDLOADER_EC_KEYS[] = {
-#if BOOTLOADER_DEVEL
- (const uint8_t*) "\xdb\x99\x5f\xe2\x51\x69\xd1\x41\xca\xb9\xbb\xba\x92\xba\xa0\x1f\x9f\x2e\x1e\xce\x7d\xf4\xcb\x2a\xc0\x51\x90\xf3\x7f\xcc\x1f\x9d",
- (const uint8_t*) "\x21\x52\xf8\xd1\x9b\x79\x1d\x24\x45\x32\x42\xe1\x5f\x2e\xab\x6c\xb7\xcf\xfa\x7b\x6a\x5e\xd3\x00\x97\x96\x0e\x06\x98\x81\xdb\x12",
-#else
- MODEL_BOARDLOADER_EC_KEYS
-#endif
-};
-
-secbool boot_header_check_signature(const boot_header_auth_t* hdr,
- const merkle_proof_node_t* merkle_root) {
- // Get the signature indices based on the signature mask
- _Static_assert(ARRAY_LENGTH(BOARDLOADER_PQ_KEYS) <= 3);
- _Static_assert(ARRAY_LENGTH(BOARDLOADER_EC_KEYS) ==
- ARRAY_LENGTH(BOARDLOADER_PQ_KEYS));
-
- uint8_t sigmask = hdr->sigmask;
- uint8_t sigmask_inv = 0; // FIH
-
- const boot_header_unauth_t* sig = boot_header_unauth_get(hdr);
-
- for (int sig_idx = 0; sig_idx < ARRAY_LENGTH(sig->ec_signature); sig_idx++) {
- // Get the index of the public key in the signature mask
- int key_idx = __builtin_ctz(sigmask);
- if (key_idx >= ARRAY_LENGTH(BOARDLOADER_PQ_KEYS)) {
- return secfalse;
- }
-
- // Hash of the Merkle root and the SLH signature
- uint8_t hash[IMAGE_HASH_DIGEST_LENGTH];
- IMAGE_HASH_CTX ctx;
- IMAGE_HASH_INIT(&ctx);
- IMAGE_HASH_UPDATE(&ctx, merkle_root->bytes, sizeof(merkle_root->bytes));
- IMAGE_HASH_UPDATE(&ctx, sig->slh_signature[sig_idx],
- sizeof(sig->slh_signature[sig_idx]));
- IMAGE_HASH_FINAL(&ctx, hash);
-
- // Verify EC signature - do it before we verify the PQC signature
- int ec_result =
- ed25519_sign_open(hash, sizeof(hash), BOARDLOADER_EC_KEYS[key_idx],
- sig->ec_signature[sig_idx]);
-
- if (ec_result != 0) {
- return secfalse;
- }
-
- // Verify the PQC signature
- int pq_result = crypto_sign_verify(
- sig->slh_signature[sig_idx], sizeof(sig->slh_signature[sig_idx]),
- merkle_root->bytes, sizeof(merkle_root->bytes),
- BOARDLOADER_PQ_KEYS[key_idx]);
-
- if (pq_result != 0) {
- return secfalse;
- }
-
- // Mark the key as used
- sigmask &= ~(1 << key_idx);
- sigmask_inv |= (1 << key_idx);
- }
-
- if (sigmask != 0 || sigmask_inv != hdr->sigmask) { // FIH
- // There were more than BOOT_HEADER_SIGNATURE_COUNT public key bits
- // set or some of the public keys in the original sigmask were not used.
- return secfalse;
- }
-
- return sectrue;
-}
-
-static size_t boot_header_merkle_proof_size(
- const boot_header_merkle_proof_t* proof) {
- return sizeof(boot_header_merkle_proof_t) +
- proof->node_count * sizeof(proof->nodes[0]);
-}
-
-static const boot_header_merkle_proof_t* boot_header_get_merkle_proof(
- const boot_header_auth_t* hdr) {
- // Check if the merkle_proof.path_len field is within the header
- if (hdr->auth_size + sizeof(boot_header_merkle_proof_t) > hdr->header_size) {
- return NULL;
- }
-
- // Merkle proof is located right after the authenticated part of the header
- boot_header_merkle_proof_t* proof =
- (boot_header_merkle_proof_t*)((uintptr_t)hdr + hdr->auth_size);
-
- // Check if the path length is in reasonable limits
- if (proof->node_count > BOOT_HEADER_MERKLE_PROOF_MAXLEN) {
- return NULL;
- }
-
- size_t proof_size = boot_header_merkle_proof_size(proof);
-
- // Check if the Merkle proof is completely within the header
- if (hdr->auth_size + proof_size > hdr->header_size) {
- return NULL;
- }
-
- return proof;
-}
-
-const boot_header_auth_t* boot_header_auth_get(uint32_t address) {
- boot_header_auth_t* hdr = (boot_header_auth_t*)address;
-
- // Check if the header starts with the magic
- if (hdr->magic != BOOT_HEADER_MAGIC_TRZQ) {
- return NULL;
- }
-
- // Check if the header size (= bootloader code offset) is aligned to 8K
- // boundary (flash page size)
- if (!IS_ALIGNED(hdr->header_size, SIZE_8K) || hdr->header_size == 0) {
- return NULL;
- }
-
- // Check if the header size is in reasonable limits
- if (hdr->header_size >= SIZE_64K) {
- return NULL;
- }
-
- // Check if the authenticated part size is within the header size
- if (hdr->auth_size >= hdr->header_size) {
- return NULL;
- }
-
- // Check if the size of the authenticated part is at least the size of the
- // authenticated boot header structure. This condition prevents updating
- // to an image whose authenticated part is smaller than the current
- // authenticated boot header structure.
- if (hdr->auth_size < sizeof(boot_header_auth_t)) {
- return NULL;
- }
-
- // Check if bootloader code size is within reasonable limits
- if (hdr->code_size < SIZE_8K) {
- return NULL;
- }
-
- // Check if the hardware model and revision match
- if (hdr->hw_model != HW_MODEL || hdr->hw_revision != HW_REVISION) {
- return secfalse;
- }
-
- // Check if the header contains a valid Merkle proof
- if (NULL == boot_header_get_merkle_proof(hdr)) {
- return NULL;
- }
-
- // Check if the header contains a valid unauthenticated part
- if (NULL == boot_header_unauth_get(hdr)) {
- return NULL;
- }
-
- return hdr;
-}
-
-const boot_header_unauth_t* boot_header_unauth_get(
- const boot_header_auth_t* hdr) {
- const boot_header_merkle_proof_t* proof = boot_header_get_merkle_proof(hdr);
-
- if (proof == NULL) {
- // If the Merkle proof is invalid, the unauthenticated part cannot
- // be valid either
- return NULL;
- }
-
- size_t proof_size = boot_header_merkle_proof_size(proof);
-
- // Unauthenticated part is located right after the Merkle proof
- boot_header_unauth_t* unauth =
- (boot_header_unauth_t*)((uintptr_t)proof + proof_size);
-
- // Check if the unauthenticated part is within the header
- if (hdr->auth_size + proof_size + sizeof(boot_header_unauth_t) >
- hdr->header_size) {
- return NULL;
- }
-
- return unauth;
-}
-
-void boot_header_calc_merkle_root(const boot_header_auth_t* hdr,
- uint32_t code_address,
- merkle_proof_node_t* root) {
- IMAGE_HASH_CTX ctx;
-
- static const uint8_t prefix0[] = {0x00};
- static const uint8_t prefix1[] = {0x01};
-
- // Hash the bootloader code
- IMAGE_HASH_INIT(&ctx);
- IMAGE_HASH_UPDATE(&ctx, (const uint8_t*)code_address, hdr->code_size);
- IMAGE_HASH_FINAL(&ctx, root->bytes);
-
- // Hash the authenticated part of the header
- IMAGE_HASH_INIT(&ctx);
- IMAGE_HASH_UPDATE(&ctx, prefix0, sizeof(prefix0));
- IMAGE_HASH_UPDATE(&ctx, (const uint8_t*)hdr, hdr->auth_size);
- IMAGE_HASH_UPDATE(&ctx, root->bytes, sizeof(root->bytes));
- IMAGE_HASH_FINAL(&ctx, root->bytes);
-
- const boot_header_merkle_proof_t* proof = boot_header_get_merkle_proof(hdr);
-
- // Add the Merkle proof nodes to the hash
- for (size_t i = 0; i < proof->node_count; i++) {
- const merkle_proof_node_t* node = &proof->nodes[i];
- IMAGE_HASH_INIT(&ctx);
- IMAGE_HASH_UPDATE(&ctx, prefix1, sizeof(prefix1));
- if (memcmp(node, root->bytes, sizeof(root->bytes)) < 0) {
- IMAGE_HASH_UPDATE(&ctx, node->bytes, sizeof(node->bytes));
- IMAGE_HASH_UPDATE(&ctx, root->bytes, sizeof(root->bytes));
- } else {
- IMAGE_HASH_UPDATE(&ctx, root->bytes, sizeof(root->bytes));
- IMAGE_HASH_UPDATE(&ctx, node->bytes, sizeof(node->bytes));
- }
- IMAGE_HASH_FINAL(&ctx, root->bytes);
- }
-}
-
-secbool bootloader_area_needs_update(const boot_header_auth_t* hdr,
- uint32_t code_address) {
- boot_header_auth_t* prev_hdr = (boot_header_auth_t*)BOOTLOADER_START;
- if (hdr->header_size == prev_hdr->header_size &&
- hdr->code_size == prev_hdr->code_size &&
- (memcmp(hdr, prev_hdr, hdr->header_size) == 0) &&
- (memcmp((const uint8_t*)code_address,
- (const uint8_t*)prev_hdr + prev_hdr->header_size,
- hdr->code_size) == 0)) {
- return secfalse;
- }
- return sectrue;
-}
-
-#endif // SECURE_MODE
diff --git a/core/embed/sec/image/boot_image.c b/core/embed/sec/image/boot_image.c
deleted file mode 100644
index b3e100f1..00000000
--- a/core/embed/sec/image/boot_image.c
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-#ifdef SECURE_MODE
-
-#include <trezor_model.h>
-#include <trezor_rtl.h>
-
-#include <sec/board_capabilities.h>
-#include <sec/boot_image.h>
-#include <sec/image.h>
-#include <sec/monoctr.h>
-#include <sys/flash.h>
-#include <sys/mpu.h>
-
-#include "blake2s.h"
-#include "memzero.h"
-
-#ifdef USE_BOOT_UCB
-#include <sec/boot_header.h>
-#include <sec/boot_ucb.h>
-#else
-#include "uzlib.h"
-#endif
-
-#ifndef USE_BOOT_UCB
-
-static secbool hash_match(const uint8_t *hash, const uint8_t *hash_00,
- const uint8_t *hash_FF) {
- if (0 == memcmp(hash, hash_00, BLAKE2S_DIGEST_LENGTH)) return sectrue;
- if (0 == memcmp(hash, hash_FF, BLAKE2S_DIGEST_LENGTH)) return sectrue;
- return secfalse;
-}
-
-#define UZLIB_WINDOW_SIZE (1 << 10)
-_Static_assert(
- UZLIB_WINDOW_SIZE >= IMAGE_HEADER_SIZE,
- "UZLIB_WINDOW_SIZE must be at least as large as IMAGE_HEADER_SIZE");
-_Static_assert(
- BOOTLOADER_MAXSIZE <= IMAGE_CHUNK_SIZE,
- "BOOTLOADER_MAXSIZE must be less than or equal to IMAGE_CHUNK_SIZE");
-
-static void uzlib_prepare(struct uzlib_uncomp *decomp, uint8_t *window,
- const void *src, uint32_t srcsize, void *dest,
- uint32_t destsize) {
- memzero(decomp, sizeof(struct uzlib_uncomp));
- if (window) {
- memzero(window, UZLIB_WINDOW_SIZE);
- }
- memzero(dest, destsize);
- decomp->source = (const uint8_t *)src;
- decomp->source_limit = decomp->source + srcsize;
- decomp->dest = (uint8_t *)dest;
- decomp->dest_limit = decomp->dest + destsize;
- uzlib_uncompress_init(decomp, window, window ? UZLIB_WINDOW_SIZE : 0);
-}
-
-bool boot_image_check(const boot_image_t *image) {
- mpu_mode_t mode = mpu_reconfig(MPU_MODE_BOOTLOADER);
-
- // compute current bootloader hash
- uint8_t hash[BLAKE2S_DIGEST_LENGTH];
- const uint32_t bl_len = flash_area_get_size(&BOOTLOADER_AREA);
- const void *bl_data = flash_area_get_address(&BOOTLOADER_AREA, 0, bl_len);
- blake2s(bl_data, bl_len, hash, BLAKE2S_DIGEST_LENGTH);
-
- // don't whitelist the valid bootloaders for now
- // ensure(known_bootloader(hash, BLAKE2S_DIGEST_LENGTH), "Unknown bootloader
- // detected");
-
- // does the bootloader match?
- if (sectrue == hash_match(hash, image->hash_00, image->hash_FF)) {
- mpu_reconfig(mode);
- return false;
- }
-
- mpu_reconfig(mode);
- return true;
-}
-
-void boot_image_replace(const boot_image_t *image) {
- const uint32_t bl_len = flash_area_get_size(&BOOTLOADER_AREA);
- const void *bl_data = flash_area_get_address(&BOOTLOADER_AREA, 0, bl_len);
-
- mpu_mode_t mode = mpu_reconfig(MPU_MODE_BOOTLOADER);
-
- struct uzlib_uncomp decomp = {0};
- uint8_t decomp_window[UZLIB_WINDOW_SIZE] = {0};
- uint32_t decomp_out[IMAGE_HEADER_SIZE / sizeof(uint32_t)] = {0};
-
- uzlib_prepare(&decomp, decomp_window, image->image_ptr, image->image_size,
- decomp_out, sizeof(decomp_out));
-
- ensure((uzlib_uncompress(&decomp) == TINF_OK) ? sectrue : secfalse,
- "Bootloader header decompression failed");
-
- const image_header *new_bld_hdr = read_image_header(
- (uint8_t *)&decomp_out, BOOTLOADER_IMAGE_MAGIC, BOOTLOADER_MAXSIZE);
-
- ensure(new_bld_hdr == (const image_header *)&decomp_out ? sectrue : secfalse,
- "Invalid embedded bootloader");
-
- ensure(check_image_model(new_bld_hdr), "Incompatible embedded bootloader");
-
- ensure(check_bootloader_header_sig(new_bld_hdr),
- "Invalid embedded bootloader signature");
-
- const image_header *current_bld_hdr =
- read_image_header(bl_data, BOOTLOADER_IMAGE_MAGIC, BOOTLOADER_MAXSIZE);
-
- uint8_t new_bld_hash[IMAGE_HASH_DIGEST_LENGTH] = {0};
- uint8_t new_bld_hash_expected[IMAGE_HASH_DIGEST_LENGTH] = {0};
-
- IMAGE_HASH_CTX ctx;
- IMAGE_HASH_INIT(&ctx);
-
- // backup new bld header values, as it will be overwritten by uzlib
- size_t header_offset = new_bld_hdr->hdrlen;
- memcpy(new_bld_hash_expected, &new_bld_hdr->hashes[0],
- IMAGE_HASH_DIGEST_LENGTH);
- uint32_t new_bld_hw_model = new_bld_hdr->hw_model;
- uint8_t new_bld_monotonic = new_bld_hdr->monotonic;
-
- do {
- uint8_t *p = (uint8_t *)decomp_out + header_offset;
- uint32_t size = decomp.dest - (uint8_t *)decomp_out - header_offset;
- IMAGE_HASH_UPDATE(&ctx, p, size);
- decomp.dest = (uint8_t *)decomp_out;
- header_offset =
- 0; // after the first chunk, we don't need to skip the header anymore
- } while (uzlib_uncompress(&decomp) >= 0);
-
- IMAGE_HASH_FINAL(&ctx, new_bld_hash);
-
- if (0 !=
- memcmp(new_bld_hash, new_bld_hash_expected, IMAGE_HASH_DIGEST_LENGTH)) {
- // the hash in the header does not match the computed hash
- error_shutdown("Invalid bootloader contents");
- }
-
- memset(&decomp, 0, sizeof(struct uzlib_uncomp));
-
- // cannot find valid header for current bootloader, something is wrong
- ensure(current_bld_hdr == (const image_header *)bl_data ? sectrue : secfalse,
- "Invalid bootloader header");
-
- ensure(check_image_model(current_bld_hdr), "Incompatible bootloader found");
-
- if (new_bld_monotonic < current_bld_hdr->monotonic) {
- error_shutdown("Bootloader downgrade rejected");
- }
-
- uint32_t board_name = get_board_name();
- if (board_name == 0 || strncmp((const char *)&board_name, "T2T1", 4) == 0) {
- // no board capabilities, assume Model T
- if ((strncmp((const char *)&new_bld_hw_model, "T2T1", 4) != 0) &&
- (new_bld_hw_model != 0)) {
- // reject non-model T bootloader
- // 0 represents pre-model check bootloader
- error_shutdown("Incompatible embedded bootloader");
- }
- }
- // at this point, due to the previous check_image_model call, we know that the
- // new_bld_hdr is
- // meant for the same model as this firmware, so we can check the board name
- // against the firmware hw_model.
- else if (board_name != HW_MODEL) {
- // reject incompatible bootloader
- error_shutdown("Incompatible embedded bootloader");
- }
-
- ensure(flash_area_erase(&BOOTLOADER_AREA, NULL), NULL);
- ensure(flash_unlock_write(), NULL);
-
- uint32_t offset = 0;
-
- uzlib_prepare(&decomp, decomp_window, image->image_ptr, image->image_size,
- decomp_out, sizeof(decomp_out));
-
- ensure((uzlib_uncompress(&decomp) == TINF_OK) ? sectrue : secfalse,
- "Bootloader decompression failed");
-
- do {
- uint32_t *p = decomp_out;
- uint32_t size = decomp.dest - (uint8_t *)decomp_out;
- uint32_t size_padded = FLASH_ALIGN(size);
- ensure(flash_area_write_data_padded(&BOOTLOADER_AREA, offset, p, size, 0,
- size_padded),
- NULL);
- offset += size_padded;
- decomp.dest = (uint8_t *)decomp_out;
- } while (uzlib_uncompress(&decomp) >= 0);
-
- if (offset < bl_len) {
- // fill the rest of the bootloader area with 0x00
- ensure(flash_area_write_data_padded(&BOOTLOADER_AREA, offset, NULL, 0, 0,
- bl_len - offset),
- NULL);
- }
-
- ensure(flash_lock_write(), NULL);
-
- mpu_reconfig(mode);
-}
-
-#else
-
-bool boot_image_check(const boot_image_t *image) {
- if (image->image_size < sizeof(boot_header_auth_t)) {
- // Invalid image size, must be at least the size of the header
- return false;
- }
-
- mpu_mode_t mode = mpu_reconfig(MPU_MODE_BOOTLOADER);
-
- boot_header_auth_t *cur_hdr = (boot_header_auth_t *)BOOTLOADER_START;
- boot_header_auth_t *new_hdr = (boot_header_auth_t *)image->image_ptr;
-
- bool diff = (cur_hdr->header_size != new_hdr->header_size) ||
- (memcmp(cur_hdr, new_hdr, cur_hdr->header_size) != 0);
-
- mpu_restore(mode);
-
- return diff;
-}
-
-void boot_image_replace(const boot_image_t *image) {
- uint32_t header_address = (uint32_t)image->image_ptr;
-
- // Check that image is big enough to hold the header at least
- ensure(sectrue * (image->image_size >= sizeof(boot_header_auth_t)),
- "Bootloader image too small");
-
- // Read bootloader header
- const boot_header_auth_t *hdr = boot_header_auth_get(header_address);
- ensure((hdr != NULL) * sectrue, "Invalid bootloader header");
-
- // Check the image is big enough to hold both header and code
- ensure(sectrue * (hdr->header_size + hdr->code_size <= image->image_size),
- "Bootloader image too small");
-
- // Check monotonic version
-
- mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_BOOTLOADER);
-
- const boot_header_auth_t *old_hdr = boot_header_auth_get(BOOTLOADER_START);
-
- ensure((old_hdr != NULL) * sectrue, "Invalid current bootloader header");
-
- uint8_t min_monotonic_version = old_hdr->monotonic_version;
-
- mpu_restore(mpu_mode);
-
- ensure(sectrue * (hdr->monotonic_version >= min_monotonic_version),
- "Bootloader downgrade rejected");
-
- uint32_t code_address = (uint32_t)image->image_ptr + hdr->header_size;
-
- // Calculate the Merkle root from the header and the code
- merkle_proof_node_t merkle_root;
- boot_header_calc_merkle_root(hdr, code_address, &merkle_root);
-
- // Check whether the new bootloader is properly signed
- ensure(boot_header_check_signature(hdr, &merkle_root),
- "Invalid bootloader signature");
-
- // Write to update control block
- ensure(boot_ucb_write(header_address, code_address),
- "Failed to write boot UCB");
-}
-
-#endif
-
-#endif
diff --git a/core/embed/sec/image/boot_ucb.c b/core/embed/sec/image/boot_ucb.c
deleted file mode 100644
index bed3875b..00000000
--- a/core/embed/sec/image/boot_ucb.c
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-#ifdef SECURE_MODE
-
-#include <trezor_bsp.h>
-#include <trezor_model.h>
-#include <trezor_rtl.h>
-
-#include <rtl/sizedefs.h>
-#include <sec/boot_header.h>
-#include <sec/boot_ucb.h>
-#include <sec/image_hash_conf.h>
-#include <sys/flash.h>
-#include <sys/mpu.h>
-
-#define BOOT_UCB_MAGIC 0x5A8C7BF3
-
-#if defined(BOOTLOADER) || defined(BOARDLOADER)
-void adjust_to_secure_flash(uint32_t* address) {
-#ifndef TREZOR_EMULATOR
- if (*address < FLASH_BASE_S) {
- // Address is in the non-secure flash region, adjust it to point to the
- // secure flash region.
- *address += FLASH_BASE_S - FLASH_BASE_NS;
- }
-#endif
-}
-#endif
-
-secbool boot_ucb_read(boot_ucb_t* ucb) {
- mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_BOOTUCB);
- *ucb = *((const boot_ucb_t*)BOOTUCB_START);
- mpu_restore(mpu_mode);
-
- if (ucb->magic != BOOT_UCB_MAGIC) {
- return secfalse;
- }
-
-#if defined(BOOTLOADER) || defined(BOARDLOADER)
- // Addresses in the UCB block may reside in both non-secure and secure
- // flash regions. We need to adjust them to point to the secure flash
- // region in order to proceed.
- adjust_to_secure_flash(&ucb->header_address);
- adjust_to_secure_flash(&ucb->code_address);
-#endif
-
- // Before reading the boot header fields, we need to ensure that it's
- // located in the valid address range.
- uint32_t min_address = NONBOARDLOADER_START;
- uint32_t max_address = NONBOARDLOADER_START + NONBOARDLOADER_MAXSIZE;
-
- if (ucb->header_address < min_address ||
- ucb->header_address > max_address - sizeof(boot_header_auth_t)) {
- return secfalse;
- }
-
- const boot_header_auth_t* hdr = (boot_header_auth_t*)ucb->header_address;
-
- // Get address range where the header and code can be located.
- // Both header and code must be inside flash area reserved for the
- // firmware and must not overlap with the address range where
- // the new bootloader will be written.
- min_address = NONBOARDLOADER_START + hdr->header_size + hdr->code_size;
- max_address = NONBOARDLOADER_START + NONBOARDLOADER_MAXSIZE;
-
- // Check if the entire boot header is within the valid address range
- if (ucb->header_address < min_address || hdr->header_size > max_address ||
- ucb->header_address > max_address - hdr->header_size) {
- return secfalse;
- }
-
- // Check if code (if present) is within the valid address range
- if (ucb->code_address != 0) {
- if (ucb->code_address < min_address || hdr->code_size > max_address ||
- ucb->code_address > max_address - hdr->code_size) {
- return secfalse;
- }
- }
-
- uint8_t hash[IMAGE_HASH_DIGEST_LENGTH]; // Hash of the header
-
- IMAGE_HASH_CTX ctx;
- IMAGE_HASH_INIT(&ctx);
- IMAGE_HASH_UPDATE(&ctx, (const uint8_t*)hdr, hdr->header_size);
- IMAGE_HASH_FINAL(&ctx, hash);
-
- if (memcmp(hash, ucb->hash, sizeof(hash)) != 0) {
- // Header hash does not match the one stored in UCB
- // This can happen if the header was modified after the UCB was written.
- return secfalse;
- }
-
- return sectrue;
-}
-
-secbool boot_ucb_write(uint32_t header_address, uint32_t code_address) {
- boot_ucb_t ucb = {
- .magic = BOOT_UCB_MAGIC,
- .header_address = header_address,
- .code_address = code_address,
- };
-
- // Calculate the hash of the header
- boot_header_auth_t* hdr = (boot_header_auth_t*)header_address;
-
- IMAGE_HASH_CTX ctx;
- IMAGE_HASH_INIT(&ctx);
- IMAGE_HASH_UPDATE(&ctx, (const uint8_t*)hdr, hdr->header_size);
- IMAGE_HASH_FINAL(&ctx, ucb.hash);
-
- secbool result = secfalse;
-
- mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_BOOTUCB);
-
- // Erase the UCB area
- if (sectrue != flash_area_erase(&BOOTUCB_AREA, NULL)) {
- goto cleanup;
- }
-
- if (sectrue != flash_unlock_write()) {
- goto cleanup;
- }
-
- // Write the UCB
- if (sectrue !=
- flash_area_write_data(&BOOTUCB_AREA, 0, (const void*)&ucb, sizeof(ucb))) {
- goto cleanup;
- }
-
- ensure(flash_lock_write(), NULL);
-
- result = sectrue;
-
-cleanup:
-
- mpu_restore(mpu_mode);
- return result;
-}
-
-secbool boot_ucb_erase(void) {
- mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_BOOTUCB);
- secbool result = flash_area_is_erased(&BOOTUCB_AREA);
- if (sectrue != result) {
- result = flash_area_erase(&BOOTUCB_AREA, NULL);
- }
- mpu_restore(mpu_mode);
- return result;
-}
-
-#endif // SECURE_MODE
diff --git a/core/embed/sec/image/stm32/boot_header.c b/core/embed/sec/image/stm32/boot_header.c
new file mode 100644
index 00000000..ec2ca636
--- /dev/null
+++ b/core/embed/sec/image/stm32/boot_header.c
@@ -0,0 +1,275 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef SECURE_MODE
+
+#include <trezor_model.h>
+#include <trezor_rtl.h>
+
+#include <rtl/sizedefs.h>
+#include <sec/boot_header.h>
+#include <sec/image_hash_conf.h>
+
+#include <../vendor/sphincsplus/ref/api.h>
+#include <ed25519-donna/ed25519.h>
+
+static const uint8_t * const BOARDLOADER_PQ_KEYS[] = {
+#if BOOTLOADER_DEVEL
+ (const uint8_t*) "\xec\x01\xe6\x02\x63\x02\x4f\x7e\x71\x72\x80\x13\xb7\x31\xf7\xba\x12\x99\xf5\x18\xc2\x7b\xa3\xed\x8f\x4a\x21\x99\x74\x12\x7c\x62",
+ (const uint8_t*) "\x8a\xf8\x87\x80\x85\x94\x6e\xd8\xb1\x16\xbd\x24\xc0\xf2\xaa\xc4\x8b\x7e\x8f\x11\xbf\x06\x87\x25\xcc\xfb\xb1\x52\xab\xf7\xa4\xcd",
+#else
+ MODEL_BOARDLOADER_PQ_KEYS
+#endif
+};
+
+static const uint8_t * const BOARDLOADER_EC_KEYS[] = {
+#if BOOTLOADER_DEVEL
+ (const uint8_t*) "\xdb\x99\x5f\xe2\x51\x69\xd1\x41\xca\xb9\xbb\xba\x92\xba\xa0\x1f\x9f\x2e\x1e\xce\x7d\xf4\xcb\x2a\xc0\x51\x90\xf3\x7f\xcc\x1f\x9d",
+ (const uint8_t*) "\x21\x52\xf8\xd1\x9b\x79\x1d\x24\x45\x32\x42\xe1\x5f\x2e\xab\x6c\xb7\xcf\xfa\x7b\x6a\x5e\xd3\x00\x97\x96\x0e\x06\x98\x81\xdb\x12",
+#else
+ MODEL_BOARDLOADER_EC_KEYS
+#endif
+};
+
+secbool boot_header_check_signature(const boot_header_auth_t* hdr,
+ const merkle_proof_node_t* merkle_root) {
+ // Get the signature indices based on the signature mask
+ _Static_assert(ARRAY_LENGTH(BOARDLOADER_PQ_KEYS) <= 3);
+ _Static_assert(ARRAY_LENGTH(BOARDLOADER_EC_KEYS) ==
+ ARRAY_LENGTH(BOARDLOADER_PQ_KEYS));
+
+ uint8_t sigmask = hdr->sigmask;
+ uint8_t sigmask_inv = 0; // FIH
+
+ const boot_header_unauth_t* sig = boot_header_unauth_get(hdr);
+
+ for (int sig_idx = 0; sig_idx < ARRAY_LENGTH(sig->ec_signature); sig_idx++) {
+ // Get the index of the public key in the signature mask
+ int key_idx = __builtin_ctz(sigmask);
+ if (key_idx >= ARRAY_LENGTH(BOARDLOADER_PQ_KEYS)) {
+ return secfalse;
+ }
+
+ // Hash of the Merkle root and the SLH signature
+ uint8_t hash[IMAGE_HASH_DIGEST_LENGTH];
+ IMAGE_HASH_CTX ctx;
+ IMAGE_HASH_INIT(&ctx);
+ IMAGE_HASH_UPDATE(&ctx, merkle_root->bytes, sizeof(merkle_root->bytes));
+ IMAGE_HASH_UPDATE(&ctx, sig->slh_signature[sig_idx],
+ sizeof(sig->slh_signature[sig_idx]));
+ IMAGE_HASH_FINAL(&ctx, hash);
+
+ // Verify EC signature - do it before we verify the PQC signature
+ int ec_result =
+ ed25519_sign_open(hash, sizeof(hash), BOARDLOADER_EC_KEYS[key_idx],
+ sig->ec_signature[sig_idx]);
+
+ if (ec_result != 0) {
+ return secfalse;
+ }
+
+ // Verify the PQC signature
+ int pq_result = crypto_sign_verify(
+ sig->slh_signature[sig_idx], sizeof(sig->slh_signature[sig_idx]),
+ merkle_root->bytes, sizeof(merkle_root->bytes),
+ BOARDLOADER_PQ_KEYS[key_idx]);
+
+ if (pq_result != 0) {
+ return secfalse;
+ }
+
+ // Mark the key as used
+ sigmask &= ~(1 << key_idx);
+ sigmask_inv |= (1 << key_idx);
+ }
+
+ if (sigmask != 0 || sigmask_inv != hdr->sigmask) { // FIH
+ // There were more than BOOT_HEADER_SIGNATURE_COUNT public key bits
+ // set or some of the public keys in the original sigmask were not used.
+ return secfalse;
+ }
+
+ return sectrue;
+}
+
+static size_t boot_header_merkle_proof_size(
+ const boot_header_merkle_proof_t* proof) {
+ return sizeof(boot_header_merkle_proof_t) +
+ proof->node_count * sizeof(proof->nodes[0]);
+}
+
+static const boot_header_merkle_proof_t* boot_header_get_merkle_proof(
+ const boot_header_auth_t* hdr) {
+ // Check if the merkle_proof.path_len field is within the header
+ if (hdr->auth_size + sizeof(boot_header_merkle_proof_t) > hdr->header_size) {
+ return NULL;
+ }
+
+ // Merkle proof is located right after the authenticated part of the header
+ boot_header_merkle_proof_t* proof =
+ (boot_header_merkle_proof_t*)((uintptr_t)hdr + hdr->auth_size);
+
+ // Check if the path length is in reasonable limits
+ if (proof->node_count > BOOT_HEADER_MERKLE_PROOF_MAXLEN) {
+ return NULL;
+ }
+
+ size_t proof_size = boot_header_merkle_proof_size(proof);
+
+ // Check if the Merkle proof is completely within the header
+ if (hdr->auth_size + proof_size > hdr->header_size) {
+ return NULL;
+ }
+
+ return proof;
+}
+
+const boot_header_auth_t* boot_header_auth_get(uint32_t address) {
+ boot_header_auth_t* hdr = (boot_header_auth_t*)address;
+
+ // Check if the header starts with the magic
+ if (hdr->magic != BOOT_HEADER_MAGIC_TRZQ) {
+ return NULL;
+ }
+
+ // Check if the header size (= bootloader code offset) is aligned to 8K
+ // boundary (flash page size)
+ if (!IS_ALIGNED(hdr->header_size, SIZE_8K) || hdr->header_size == 0) {
+ return NULL;
+ }
+
+ // Check if the header size is in reasonable limits
+ if (hdr->header_size >= SIZE_64K) {
+ return NULL;
+ }
+
+ // Check if the authenticated part size is within the header size
+ if (hdr->auth_size >= hdr->header_size) {
+ return NULL;
+ }
+
+ // Check if the size of the authenticated part is at least the size of the
+ // authenticated boot header structure. This condition prevents updating
+ // to an image whose authenticated part is smaller than the current
+ // authenticated boot header structure.
+ if (hdr->auth_size < sizeof(boot_header_auth_t)) {
+ return NULL;
+ }
+
+ // Check if bootloader code size is within reasonable limits
+ if (hdr->code_size < SIZE_8K) {
+ return NULL;
+ }
+
+ // Check if the hardware model and revision match
+ if (hdr->hw_model != HW_MODEL || hdr->hw_revision != HW_REVISION) {
+ return secfalse;
+ }
+
+ // Check if the header contains a valid Merkle proof
+ if (NULL == boot_header_get_merkle_proof(hdr)) {
+ return NULL;
+ }
+
+ // Check if the header contains a valid unauthenticated part
+ if (NULL == boot_header_unauth_get(hdr)) {
+ return NULL;
+ }
+
+ return hdr;
+}
+
+const boot_header_unauth_t* boot_header_unauth_get(
+ const boot_header_auth_t* hdr) {
+ const boot_header_merkle_proof_t* proof = boot_header_get_merkle_proof(hdr);
+
+ if (proof == NULL) {
+ // If the Merkle proof is invalid, the unauthenticated part cannot
+ // be valid either
+ return NULL;
+ }
+
+ size_t proof_size = boot_header_merkle_proof_size(proof);
+
+ // Unauthenticated part is located right after the Merkle proof
+ boot_header_unauth_t* unauth =
+ (boot_header_unauth_t*)((uintptr_t)proof + proof_size);
+
+ // Check if the unauthenticated part is within the header
+ if (hdr->auth_size + proof_size + sizeof(boot_header_unauth_t) >
+ hdr->header_size) {
+ return NULL;
+ }
+
+ return unauth;
+}
+
+void boot_header_calc_merkle_root(const boot_header_auth_t* hdr,
+ uint32_t code_address,
+ merkle_proof_node_t* root) {
+ IMAGE_HASH_CTX ctx;
+
+ static const uint8_t prefix0[] = {0x00};
+ static const uint8_t prefix1[] = {0x01};
+
+ // Hash the bootloader code
+ IMAGE_HASH_INIT(&ctx);
+ IMAGE_HASH_UPDATE(&ctx, (const uint8_t*)code_address, hdr->code_size);
+ IMAGE_HASH_FINAL(&ctx, root->bytes);
+
+ // Hash the authenticated part of the header
+ IMAGE_HASH_INIT(&ctx);
+ IMAGE_HASH_UPDATE(&ctx, prefix0, sizeof(prefix0));
+ IMAGE_HASH_UPDATE(&ctx, (const uint8_t*)hdr, hdr->auth_size);
+ IMAGE_HASH_UPDATE(&ctx, root->bytes, sizeof(root->bytes));
+ IMAGE_HASH_FINAL(&ctx, root->bytes);
+
+ const boot_header_merkle_proof_t* proof = boot_header_get_merkle_proof(hdr);
+
+ // Add the Merkle proof nodes to the hash
+ for (size_t i = 0; i < proof->node_count; i++) {
+ const merkle_proof_node_t* node = &proof->nodes[i];
+ IMAGE_HASH_INIT(&ctx);
+ IMAGE_HASH_UPDATE(&ctx, prefix1, sizeof(prefix1));
+ if (memcmp(node, root->bytes, sizeof(root->bytes)) < 0) {
+ IMAGE_HASH_UPDATE(&ctx, node->bytes, sizeof(node->bytes));
+ IMAGE_HASH_UPDATE(&ctx, root->bytes, sizeof(root->bytes));
+ } else {
+ IMAGE_HASH_UPDATE(&ctx, root->bytes, sizeof(root->bytes));
+ IMAGE_HASH_UPDATE(&ctx, node->bytes, sizeof(node->bytes));
+ }
+ IMAGE_HASH_FINAL(&ctx, root->bytes);
+ }
+}
+
+secbool bootloader_area_needs_update(const boot_header_auth_t* hdr,
+ uint32_t code_address) {
+ boot_header_auth_t* prev_hdr = (boot_header_auth_t*)BOOTLOADER_START;
+ if (hdr->header_size == prev_hdr->header_size &&
+ hdr->code_size == prev_hdr->code_size &&
+ (memcmp(hdr, prev_hdr, hdr->header_size) == 0) &&
+ (memcmp((const uint8_t*)code_address,
+ (const uint8_t*)prev_hdr + prev_hdr->header_size,
+ hdr->code_size) == 0)) {
+ return secfalse;
+ }
+ return sectrue;
+}
+
+#endif // SECURE_MODE
diff --git a/core/embed/sec/image/stm32/boot_image.c b/core/embed/sec/image/stm32/boot_image.c
new file mode 100644
index 00000000..b3e100f1
--- /dev/null
+++ b/core/embed/sec/image/stm32/boot_image.c
@@ -0,0 +1,290 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef SECURE_MODE
+
+#include <trezor_model.h>
+#include <trezor_rtl.h>
+
+#include <sec/board_capabilities.h>
+#include <sec/boot_image.h>
+#include <sec/image.h>
+#include <sec/monoctr.h>
+#include <sys/flash.h>
+#include <sys/mpu.h>
+
+#include "blake2s.h"
+#include "memzero.h"
+
+#ifdef USE_BOOT_UCB
+#include <sec/boot_header.h>
+#include <sec/boot_ucb.h>
+#else
+#include "uzlib.h"
+#endif
+
+#ifndef USE_BOOT_UCB
+
+static secbool hash_match(const uint8_t *hash, const uint8_t *hash_00,
+ const uint8_t *hash_FF) {
+ if (0 == memcmp(hash, hash_00, BLAKE2S_DIGEST_LENGTH)) return sectrue;
+ if (0 == memcmp(hash, hash_FF, BLAKE2S_DIGEST_LENGTH)) return sectrue;
+ return secfalse;
+}
+
+#define UZLIB_WINDOW_SIZE (1 << 10)
+_Static_assert(
+ UZLIB_WINDOW_SIZE >= IMAGE_HEADER_SIZE,
+ "UZLIB_WINDOW_SIZE must be at least as large as IMAGE_HEADER_SIZE");
+_Static_assert(
+ BOOTLOADER_MAXSIZE <= IMAGE_CHUNK_SIZE,
+ "BOOTLOADER_MAXSIZE must be less than or equal to IMAGE_CHUNK_SIZE");
+
+static void uzlib_prepare(struct uzlib_uncomp *decomp, uint8_t *window,
+ const void *src, uint32_t srcsize, void *dest,
+ uint32_t destsize) {
+ memzero(decomp, sizeof(struct uzlib_uncomp));
+ if (window) {
+ memzero(window, UZLIB_WINDOW_SIZE);
+ }
+ memzero(dest, destsize);
+ decomp->source = (const uint8_t *)src;
+ decomp->source_limit = decomp->source + srcsize;
+ decomp->dest = (uint8_t *)dest;
+ decomp->dest_limit = decomp->dest + destsize;
+ uzlib_uncompress_init(decomp, window, window ? UZLIB_WINDOW_SIZE : 0);
+}
+
+bool boot_image_check(const boot_image_t *image) {
+ mpu_mode_t mode = mpu_reconfig(MPU_MODE_BOOTLOADER);
+
+ // compute current bootloader hash
+ uint8_t hash[BLAKE2S_DIGEST_LENGTH];
+ const uint32_t bl_len = flash_area_get_size(&BOOTLOADER_AREA);
+ const void *bl_data = flash_area_get_address(&BOOTLOADER_AREA, 0, bl_len);
+ blake2s(bl_data, bl_len, hash, BLAKE2S_DIGEST_LENGTH);
+
+ // don't whitelist the valid bootloaders for now
+ // ensure(known_bootloader(hash, BLAKE2S_DIGEST_LENGTH), "Unknown bootloader
+ // detected");
+
+ // does the bootloader match?
+ if (sectrue == hash_match(hash, image->hash_00, image->hash_FF)) {
+ mpu_reconfig(mode);
+ return false;
+ }
+
+ mpu_reconfig(mode);
+ return true;
+}
+
+void boot_image_replace(const boot_image_t *image) {
+ const uint32_t bl_len = flash_area_get_size(&BOOTLOADER_AREA);
+ const void *bl_data = flash_area_get_address(&BOOTLOADER_AREA, 0, bl_len);
+
+ mpu_mode_t mode = mpu_reconfig(MPU_MODE_BOOTLOADER);
+
+ struct uzlib_uncomp decomp = {0};
+ uint8_t decomp_window[UZLIB_WINDOW_SIZE] = {0};
+ uint32_t decomp_out[IMAGE_HEADER_SIZE / sizeof(uint32_t)] = {0};
+
+ uzlib_prepare(&decomp, decomp_window, image->image_ptr, image->image_size,
+ decomp_out, sizeof(decomp_out));
+
+ ensure((uzlib_uncompress(&decomp) == TINF_OK) ? sectrue : secfalse,
+ "Bootloader header decompression failed");
+
+ const image_header *new_bld_hdr = read_image_header(
+ (uint8_t *)&decomp_out, BOOTLOADER_IMAGE_MAGIC, BOOTLOADER_MAXSIZE);
+
+ ensure(new_bld_hdr == (const image_header *)&decomp_out ? sectrue : secfalse,
+ "Invalid embedded bootloader");
+
+ ensure(check_image_model(new_bld_hdr), "Incompatible embedded bootloader");
+
+ ensure(check_bootloader_header_sig(new_bld_hdr),
+ "Invalid embedded bootloader signature");
+
+ const image_header *current_bld_hdr =
+ read_image_header(bl_data, BOOTLOADER_IMAGE_MAGIC, BOOTLOADER_MAXSIZE);
+
+ uint8_t new_bld_hash[IMAGE_HASH_DIGEST_LENGTH] = {0};
+ uint8_t new_bld_hash_expected[IMAGE_HASH_DIGEST_LENGTH] = {0};
+
+ IMAGE_HASH_CTX ctx;
+ IMAGE_HASH_INIT(&ctx);
+
+ // backup new bld header values, as it will be overwritten by uzlib
+ size_t header_offset = new_bld_hdr->hdrlen;
+ memcpy(new_bld_hash_expected, &new_bld_hdr->hashes[0],
+ IMAGE_HASH_DIGEST_LENGTH);
+ uint32_t new_bld_hw_model = new_bld_hdr->hw_model;
+ uint8_t new_bld_monotonic = new_bld_hdr->monotonic;
+
+ do {
+ uint8_t *p = (uint8_t *)decomp_out + header_offset;
+ uint32_t size = decomp.dest - (uint8_t *)decomp_out - header_offset;
+ IMAGE_HASH_UPDATE(&ctx, p, size);
+ decomp.dest = (uint8_t *)decomp_out;
+ header_offset =
+ 0; // after the first chunk, we don't need to skip the header anymore
+ } while (uzlib_uncompress(&decomp) >= 0);
+
+ IMAGE_HASH_FINAL(&ctx, new_bld_hash);
+
+ if (0 !=
+ memcmp(new_bld_hash, new_bld_hash_expected, IMAGE_HASH_DIGEST_LENGTH)) {
+ // the hash in the header does not match the computed hash
+ error_shutdown("Invalid bootloader contents");
+ }
+
+ memset(&decomp, 0, sizeof(struct uzlib_uncomp));
+
+ // cannot find valid header for current bootloader, something is wrong
+ ensure(current_bld_hdr == (const image_header *)bl_data ? sectrue : secfalse,
+ "Invalid bootloader header");
+
+ ensure(check_image_model(current_bld_hdr), "Incompatible bootloader found");
+
+ if (new_bld_monotonic < current_bld_hdr->monotonic) {
+ error_shutdown("Bootloader downgrade rejected");
+ }
+
+ uint32_t board_name = get_board_name();
+ if (board_name == 0 || strncmp((const char *)&board_name, "T2T1", 4) == 0) {
+ // no board capabilities, assume Model T
+ if ((strncmp((const char *)&new_bld_hw_model, "T2T1", 4) != 0) &&
+ (new_bld_hw_model != 0)) {
+ // reject non-model T bootloader
+ // 0 represents pre-model check bootloader
+ error_shutdown("Incompatible embedded bootloader");
+ }
+ }
+ // at this point, due to the previous check_image_model call, we know that the
+ // new_bld_hdr is
+ // meant for the same model as this firmware, so we can check the board name
+ // against the firmware hw_model.
+ else if (board_name != HW_MODEL) {
+ // reject incompatible bootloader
+ error_shutdown("Incompatible embedded bootloader");
+ }
+
+ ensure(flash_area_erase(&BOOTLOADER_AREA, NULL), NULL);
+ ensure(flash_unlock_write(), NULL);
+
+ uint32_t offset = 0;
+
+ uzlib_prepare(&decomp, decomp_window, image->image_ptr, image->image_size,
+ decomp_out, sizeof(decomp_out));
+
+ ensure((uzlib_uncompress(&decomp) == TINF_OK) ? sectrue : secfalse,
+ "Bootloader decompression failed");
+
+ do {
+ uint32_t *p = decomp_out;
+ uint32_t size = decomp.dest - (uint8_t *)decomp_out;
+ uint32_t size_padded = FLASH_ALIGN(size);
+ ensure(flash_area_write_data_padded(&BOOTLOADER_AREA, offset, p, size, 0,
+ size_padded),
+ NULL);
+ offset += size_padded;
+ decomp.dest = (uint8_t *)decomp_out;
+ } while (uzlib_uncompress(&decomp) >= 0);
+
+ if (offset < bl_len) {
+ // fill the rest of the bootloader area with 0x00
+ ensure(flash_area_write_data_padded(&BOOTLOADER_AREA, offset, NULL, 0, 0,
+ bl_len - offset),
+ NULL);
+ }
+
+ ensure(flash_lock_write(), NULL);
+
+ mpu_reconfig(mode);
+}
+
+#else
+
+bool boot_image_check(const boot_image_t *image) {
+ if (image->image_size < sizeof(boot_header_auth_t)) {
+ // Invalid image size, must be at least the size of the header
+ return false;
+ }
+
+ mpu_mode_t mode = mpu_reconfig(MPU_MODE_BOOTLOADER);
+
+ boot_header_auth_t *cur_hdr = (boot_header_auth_t *)BOOTLOADER_START;
+ boot_header_auth_t *new_hdr = (boot_header_auth_t *)image->image_ptr;
+
+ bool diff = (cur_hdr->header_size != new_hdr->header_size) ||
+ (memcmp(cur_hdr, new_hdr, cur_hdr->header_size) != 0);
+
+ mpu_restore(mode);
+
+ return diff;
+}
+
+void boot_image_replace(const boot_image_t *image) {
+ uint32_t header_address = (uint32_t)image->image_ptr;
+
+ // Check that image is big enough to hold the header at least
+ ensure(sectrue * (image->image_size >= sizeof(boot_header_auth_t)),
+ "Bootloader image too small");
+
+ // Read bootloader header
+ const boot_header_auth_t *hdr = boot_header_auth_get(header_address);
+ ensure((hdr != NULL) * sectrue, "Invalid bootloader header");
+
+ // Check the image is big enough to hold both header and code
+ ensure(sectrue * (hdr->header_size + hdr->code_size <= image->image_size),
+ "Bootloader image too small");
+
+ // Check monotonic version
+
+ mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_BOOTLOADER);
+
+ const boot_header_auth_t *old_hdr = boot_header_auth_get(BOOTLOADER_START);
+
+ ensure((old_hdr != NULL) * sectrue, "Invalid current bootloader header");
+
+ uint8_t min_monotonic_version = old_hdr->monotonic_version;
+
+ mpu_restore(mpu_mode);
+
+ ensure(sectrue * (hdr->monotonic_version >= min_monotonic_version),
+ "Bootloader downgrade rejected");
+
+ uint32_t code_address = (uint32_t)image->image_ptr + hdr->header_size;
+
+ // Calculate the Merkle root from the header and the code
+ merkle_proof_node_t merkle_root;
+ boot_header_calc_merkle_root(hdr, code_address, &merkle_root);
+
+ // Check whether the new bootloader is properly signed
+ ensure(boot_header_check_signature(hdr, &merkle_root),
+ "Invalid bootloader signature");
+
+ // Write to update control block
+ ensure(boot_ucb_write(header_address, code_address),
+ "Failed to write boot UCB");
+}
+
+#endif
+
+#endif
diff --git a/core/embed/sec/image/stm32/boot_ucb.c b/core/embed/sec/image/stm32/boot_ucb.c
new file mode 100644
index 00000000..bed3875b
--- /dev/null
+++ b/core/embed/sec/image/stm32/boot_ucb.c
@@ -0,0 +1,167 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef SECURE_MODE
+
+#include <trezor_bsp.h>
+#include <trezor_model.h>
+#include <trezor_rtl.h>
+
+#include <rtl/sizedefs.h>
+#include <sec/boot_header.h>
+#include <sec/boot_ucb.h>
+#include <sec/image_hash_conf.h>
+#include <sys/flash.h>
+#include <sys/mpu.h>
+
+#define BOOT_UCB_MAGIC 0x5A8C7BF3
+
+#if defined(BOOTLOADER) || defined(BOARDLOADER)
+void adjust_to_secure_flash(uint32_t* address) {
+#ifndef TREZOR_EMULATOR
+ if (*address < FLASH_BASE_S) {
+ // Address is in the non-secure flash region, adjust it to point to the
+ // secure flash region.
+ *address += FLASH_BASE_S - FLASH_BASE_NS;
+ }
+#endif
+}
+#endif
+
+secbool boot_ucb_read(boot_ucb_t* ucb) {
+ mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_BOOTUCB);
+ *ucb = *((const boot_ucb_t*)BOOTUCB_START);
+ mpu_restore(mpu_mode);
+
+ if (ucb->magic != BOOT_UCB_MAGIC) {
+ return secfalse;
+ }
+
+#if defined(BOOTLOADER) || defined(BOARDLOADER)
+ // Addresses in the UCB block may reside in both non-secure and secure
+ // flash regions. We need to adjust them to point to the secure flash
+ // region in order to proceed.
+ adjust_to_secure_flash(&ucb->header_address);
+ adjust_to_secure_flash(&ucb->code_address);
+#endif
+
+ // Before reading the boot header fields, we need to ensure that it's
+ // located in the valid address range.
+ uint32_t min_address = NONBOARDLOADER_START;
+ uint32_t max_address = NONBOARDLOADER_START + NONBOARDLOADER_MAXSIZE;
+
+ if (ucb->header_address < min_address ||
+ ucb->header_address > max_address - sizeof(boot_header_auth_t)) {
+ return secfalse;
+ }
+
+ const boot_header_auth_t* hdr = (boot_header_auth_t*)ucb->header_address;
+
+ // Get address range where the header and code can be located.
+ // Both header and code must be inside flash area reserved for the
+ // firmware and must not overlap with the address range where
+ // the new bootloader will be written.
+ min_address = NONBOARDLOADER_START + hdr->header_size + hdr->code_size;
+ max_address = NONBOARDLOADER_START + NONBOARDLOADER_MAXSIZE;
+
+ // Check if the entire boot header is within the valid address range
+ if (ucb->header_address < min_address || hdr->header_size > max_address ||
+ ucb->header_address > max_address - hdr->header_size) {
+ return secfalse;
+ }
+
+ // Check if code (if present) is within the valid address range
+ if (ucb->code_address != 0) {
+ if (ucb->code_address < min_address || hdr->code_size > max_address ||
+ ucb->code_address > max_address - hdr->code_size) {
+ return secfalse;
+ }
+ }
+
+ uint8_t hash[IMAGE_HASH_DIGEST_LENGTH]; // Hash of the header
+
+ IMAGE_HASH_CTX ctx;
+ IMAGE_HASH_INIT(&ctx);
+ IMAGE_HASH_UPDATE(&ctx, (const uint8_t*)hdr, hdr->header_size);
+ IMAGE_HASH_FINAL(&ctx, hash);
+
+ if (memcmp(hash, ucb->hash, sizeof(hash)) != 0) {
+ // Header hash does not match the one stored in UCB
+ // This can happen if the header was modified after the UCB was written.
+ return secfalse;
+ }
+
+ return sectrue;
+}
+
+secbool boot_ucb_write(uint32_t header_address, uint32_t code_address) {
+ boot_ucb_t ucb = {
+ .magic = BOOT_UCB_MAGIC,
+ .header_address = header_address,
+ .code_address = code_address,
+ };
+
+ // Calculate the hash of the header
+ boot_header_auth_t* hdr = (boot_header_auth_t*)header_address;
+
+ IMAGE_HASH_CTX ctx;
+ IMAGE_HASH_INIT(&ctx);
+ IMAGE_HASH_UPDATE(&ctx, (const uint8_t*)hdr, hdr->header_size);
+ IMAGE_HASH_FINAL(&ctx, ucb.hash);
+
+ secbool result = secfalse;
+
+ mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_BOOTUCB);
+
+ // Erase the UCB area
+ if (sectrue != flash_area_erase(&BOOTUCB_AREA, NULL)) {
+ goto cleanup;
+ }
+
+ if (sectrue != flash_unlock_write()) {
+ goto cleanup;
+ }
+
+ // Write the UCB
+ if (sectrue !=
+ flash_area_write_data(&BOOTUCB_AREA, 0, (const void*)&ucb, sizeof(ucb))) {
+ goto cleanup;
+ }
+
+ ensure(flash_lock_write(), NULL);
+
+ result = sectrue;
+
+cleanup:
+
+ mpu_restore(mpu_mode);
+ return result;
+}
+
+secbool boot_ucb_erase(void) {
+ mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_BOOTUCB);
+ secbool result = flash_area_is_erased(&BOOTUCB_AREA);
+ if (sectrue != result) {
+ result = flash_area_erase(&BOOTUCB_AREA, NULL);
+ }
+ mpu_restore(mpu_mode);
+ return result;
+}
+
+#endif // SECURE_MODE
diff --git a/core/embed/sec/image/unix/boot_ucb.c b/core/embed/sec/image/unix/boot_ucb.c
new file mode 100644
index 00000000..5a6d7c16
--- /dev/null
+++ b/core/embed/sec/image/unix/boot_ucb.c
@@ -0,0 +1,22 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <sec/boot_ucb.h>
+
+secbool boot_ucb_erase(void) { return sectrue; }
diff --git a/core/site_scons/models/D002/discovery2.py b/core/site_scons/models/D002/discovery2.py
index fa38a2d0..a5344b85 100644
--- a/core/site_scons/models/D002/discovery2.py
+++ b/core/site_scons/models/D002/discovery2.py
@@ -63,8 +63,8 @@ def configure(
defines += [("USE_SECRET_KEYS", "1")]
if "boot_ucb" in features_wanted:
- sources += ["embed/sec/image/boot_header.c"]
- sources += ["embed/sec/image/boot_ucb.c"]
+ sources += ["embed/sec/image/stm32/boot_header.c"]
+ sources += ["embed/sec/image/stm32/boot_ucb.c"]
defines += [("USE_BOOT_UCB", "1")]
features_available.append("boot_ucb")
diff --git a/core/site_scons/models/T3W1/trezor_t3w1_revA.py b/core/site_scons/models/T3W1/trezor_t3w1_revA.py
index 18df0e2a..0fcd4aaa 100644
--- a/core/site_scons/models/T3W1/trezor_t3w1_revA.py
+++ b/core/site_scons/models/T3W1/trezor_t3w1_revA.py
@@ -71,8 +71,8 @@ def configure(
defines += [("USE_SECRET_KEYS", "1")]
if "boot_ucb" in features_wanted:
- sources += ["embed/sec/image/boot_header.c"]
- sources += ["embed/sec/image/boot_ucb.c"]
+ sources += ["embed/sec/image/stm32/boot_header.c"]
+ sources += ["embed/sec/image/stm32/boot_ucb.c"]
defines += [("USE_BOOT_UCB", "1")]
features_available.append("boot_ucb")
diff --git a/core/site_scons/models/T3W1/trezor_t3w1_revB.py b/core/site_scons/models/T3W1/trezor_t3w1_revB.py
index d4837318..a7120292 100644
--- a/core/site_scons/models/T3W1/trezor_t3w1_revB.py
+++ b/core/site_scons/models/T3W1/trezor_t3w1_revB.py
@@ -71,8 +71,8 @@ def configure(
defines += [("USE_SECRET_KEYS", "1")]
if "boot_ucb" in features_wanted:
- sources += ["embed/sec/image/boot_header.c"]
- sources += ["embed/sec/image/boot_ucb.c"]
+ sources += ["embed/sec/image/stm32/boot_header.c"]
+ sources += ["embed/sec/image/stm32/boot_ucb.c"]
defines += [("USE_BOOT_UCB", "1")]
features_available.append("boot_ucb")
diff --git a/core/site_scons/models/T3W1/trezor_t3w1_revC.py b/core/site_scons/models/T3W1/trezor_t3w1_revC.py
index 7f4c14a8..c42c8048 100644
--- a/core/site_scons/models/T3W1/trezor_t3w1_revC.py
+++ b/core/site_scons/models/T3W1/trezor_t3w1_revC.py
@@ -87,8 +87,8 @@ def configure(
sources += ["vendor/mldsa-native/mldsa/src/fips202/keccakf1600.c"]
if "boot_ucb" in features_wanted:
- sources += ["embed/sec/image/boot_header.c"]
- sources += ["embed/sec/image/boot_ucb.c"]
+ sources += ["embed/sec/image/stm32/boot_header.c"]
+ sources += ["embed/sec/image/stm32/boot_ucb.c"]
defines += [("USE_BOOT_UCB", "1")]
features_available.append("boot_ucb")
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.