1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-08-05 05:15:27 +00:00

fix(core): fix bootloader monotonic check during upgrade

[no changelog]
This commit is contained in:
tychovrahe 2025-07-23 17:20:50 +02:00 committed by cepetr
parent 2a0a8b3b3c
commit 2bd3b6ff06
16 changed files with 109 additions and 82 deletions

View File

@ -50,10 +50,10 @@
#define BOOTUCB_SECTOR_END 0xE
// Non-boardloader area (includes bootloader, firmware, assets and storage)
#define NONBLDR_START (0x0C01E000)
#define NONBLDR_MAXSIZE (497 * 8 * 1024) // 3976 kB
#define NONBLDR_SECTOR_START 0xF
#define NONBLDR_SECTOR_END 0x1FF
#define NONBOARDLOADER_START (0x0C01E000)
#define NONBOARDLOADER_MAXSIZE (497 * 8 * 1024) // 3976 kB
#define NONBOARDLOADER_SECTOR_START 0xF
#define NONBOARDLOADER_SECTOR_END 0x1FF
#define BOOTLOADER_START (0x0C01E000)
#define BOOTLOADER_MAXSIZE (32 * 8 * 1024) // 256 kB

View File

@ -19,10 +19,10 @@ BOOTUCB_START = 0xc01c000;
BOOTUCB_MAXSIZE = 0x2000;
BOOTUCB_SECTOR_START = 0xe;
BOOTUCB_SECTOR_END = 0xe;
NONBLDR_START = 0xc01e000;
NONBLDR_MAXSIZE = 0x3e2000;
NONBLDR_SECTOR_START = 0xf;
NONBLDR_SECTOR_END = 0x1ff;
NONBOARDLOADER_START = 0xc01e000;
NONBOARDLOADER_MAXSIZE = 0x3e2000;
NONBOARDLOADER_SECTOR_START = 0xf;
NONBOARDLOADER_SECTOR_END = 0x1ff;
BOOTLOADER_START = 0xc01e000;
BOOTLOADER_MAXSIZE = 0x40000;
BOOTLOADER_SECTOR_START = 0xf;

View File

@ -50,10 +50,10 @@
#define BOOTUCB_SECTOR_END 0xE
// Non-boardloader area (includes bootloader, firmware, assets and storage)
#define NONBLDR_START (0x0C01E000)
#define NONBLDR_MAXSIZE (497 * 8 * 1024) // 3976 kB
#define NONBLDR_SECTOR_START 0xF
#define NONBLDR_SECTOR_END 0x1FF
#define NONBOARDLOADER_START (0x0C01E000)
#define NONBOARDLOADER_MAXSIZE (497 * 8 * 1024) // 3976 kB
#define NONBOARDLOADER_SECTOR_START 0xF
#define NONBOARDLOADER_SECTOR_END 0x1FF
#define BOOTLOADER_START (0x0C01E000)
#define BOOTLOADER_MAXSIZE (32 * 8 * 1024) // 256 kB

View File

@ -19,10 +19,10 @@ BOOTUCB_START = 0xc01c000;
BOOTUCB_MAXSIZE = 0x2000;
BOOTUCB_SECTOR_START = 0xe;
BOOTUCB_SECTOR_END = 0xe;
NONBLDR_START = 0xc01e000;
NONBLDR_MAXSIZE = 0x3e2000;
NONBLDR_SECTOR_START = 0xf;
NONBLDR_SECTOR_END = 0x1ff;
NONBOARDLOADER_START = 0xc01e000;
NONBOARDLOADER_MAXSIZE = 0x3e2000;
NONBOARDLOADER_SECTOR_START = 0xf;
NONBOARDLOADER_SECTOR_END = 0x1ff;
BOOTLOADER_START = 0xc01e000;
BOOTLOADER_MAXSIZE = 0x40000;
BOOTLOADER_SECTOR_START = 0xf;

View File

@ -131,7 +131,7 @@ board_capabilities_t capabilities
#ifdef USE_BOOT_UCB
static void try_to_upgrade(void) {
static void try_bootloader_update(void) {
boot_ucb_t ucb;
// Start with some non-deterministic delay
@ -143,7 +143,7 @@ static void try_to_upgrade(void) {
}
// Check if the new boot header is present and valid
const boot_header_t* hdr = boot_header_check_integrity(ucb.header_address);
const boot_header_auth_t* hdr = boot_header_auth_get(ucb.header_address);
if (hdr == NULL) {
return;
}
@ -167,7 +167,7 @@ static void try_to_upgrade(void) {
// Check if the new bootloader is the same as the old one
// (just prevents unnecessary flash erase/write)
if (bootloader_is_unchanged(hdr, code_address)) {
if (sectrue != bootloader_area_needs_update(hdr, code_address)) {
return;
}
@ -240,8 +240,8 @@ static inline void ensure_signed_bootloader(
fih_delay(0);
// Check if the boot header is present and valid
const boot_header_t* hdr = boot_header_check_integrity(BOOTLOADER_START);
fih_ensure(sectrue * (hdr != NULL), "invalid bootloader header");
const boot_header_auth_t* hdr = boot_header_auth_get(BOOTLOADER_START);
fih_ensure(sectrue * (hdr != NULL), "invalid boot header");
// Get address of the bootloader code
uint32_t code_address = BOOTLOADER_START + hdr->header_size;
@ -326,7 +326,7 @@ int main(void) {
#ifdef USE_BOOT_UCB
// Try to update the bootloader from the UCB (update control block) if it
// is present, valid and points to a new valid/signed bootloader image.
try_to_upgrade();
try_bootloader_update();
#endif
// Address of the next stage to jump to. It's set at the end of

View File

@ -40,7 +40,7 @@ static void prodtest_bootloader_version(cli_t *cli) {
mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_BOOTUPDATE);
const boot_header_t *hdr = boot_header_check_integrity(BOOTLOADER_START);
const boot_header_auth_t *hdr = boot_header_auth_get(BOOTLOADER_START);
if (hdr == NULL) {
mpu_restore(mpu_mode);

View File

@ -60,6 +60,8 @@ DEFINE_SINGLE_AREA(ASSETS_AREA, ASSETS, ACCESS_APP);
#endif
#ifdef USE_BOOT_UCB
// Area dedicated to the UCB (Update Control Block) used during
// boot-loader or boot-header updates.
DEFINE_SINGLE_AREA(BOOTUCB_AREA, BOOTUCB, ACCESS_DEFAULT);
#ifdef BOARDLOADER
// Area used by the boardloader during bootloader update process.

View File

@ -35,7 +35,7 @@
extern const uint8_t _bootloader_code_size;
typedef union {
boot_header_t hdr;
boot_header_auth_t hdr;
uint8_t raw[BOOT_HEADER_MAXSIZE];
} boot_header_padded_t;
@ -100,17 +100,17 @@ static const uint8_t * const BOARDLOADER_EC_KEYS[] = {
#endif
};
secbool boot_header_check_signature(const boot_header_t* hdr,
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));
uint32_t sigmask = hdr->sigmask;
uint32_t sigmask_inv = 0; // FIH
uint8_t sigmask = hdr->sigmask;
uint8_t sigmask_inv = 0; // FIH
const boot_header_unauth_t* sig = boot_header_get_unauth(hdr);
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
@ -193,8 +193,8 @@ static const boot_header_merkle_proof_t* boot_header_get_merkle_proof(
return proof;
}
const boot_header_t* boot_header_check_integrity(uint32_t address) {
boot_header_t* hdr = (boot_header_t*)address;
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) {
@ -248,7 +248,8 @@ const boot_header_t* boot_header_check_integrity(uint32_t address) {
return hdr;
}
const boot_header_unauth_t* boot_header_get_unauth(const boot_header_t* 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) {
@ -272,7 +273,7 @@ const boot_header_unauth_t* boot_header_get_unauth(const boot_header_t* hdr) {
return unauth;
}
void boot_header_calc_merkle_root(const boot_header_t* hdr,
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;
@ -310,18 +311,18 @@ void boot_header_calc_merkle_root(const boot_header_t* hdr,
}
}
secbool bootloader_is_unchanged(const boot_header_t* hdr,
uint32_t code_address) {
boot_header_t* prev_hdr = (boot_header_t*)BOOTLOADER_START;
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 sectrue;
return secfalse;
}
return secfalse;
return sectrue;
}
#endif // SECURE_MODE

View File

@ -221,15 +221,15 @@ void boot_image_replace(const boot_image_t *image) {
#else
bool boot_image_check(const boot_image_t *image) {
if (image->image_size < sizeof(boot_header_t)) {
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_BOOTUPDATE);
boot_header_t *cur_hdr = (boot_header_t *)BOOTLOADER_START;
boot_header_t *new_hdr = (boot_header_t *)image->image_ptr;
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);
@ -243,25 +243,29 @@ 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_t)),
ensure(sectrue * (image->image_size >= sizeof(boot_header_auth_t)),
"Bootloader image too small");
// Read bootloader header
const boot_header_t *hdr = boot_header_check_integrity(header_address);
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 <= image->image_size),
"Bootloader header too big");
ensure(sectrue * (hdr->code_size <= image->image_size),
"Bootloader code too big");
ensure(sectrue * (hdr->header_size + hdr->code_size <= image->image_size),
"Bootloader image too small");
// Check monotonic version
uint8_t min_monotonic_version = 0;
ensure(monoctr_read(MONOCTR_BOOTLOADER_VERSION, &min_monotonic_version),
"monoctr read");
mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_BOOTUPDATE);
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");

View File

@ -42,8 +42,10 @@ extern const void bootloader_size;
static const boot_image_t g_bootloader_image = {
.image_ptr = (const void *)&bootloader_start,
.image_size = (size_t)&bootloader_size,
#ifndef USE_BOOT_UCB
.hash_00 = BOOTLOADER_00,
.hash_FF = BOOTLOADER_FF,
#endif
};
const boot_image_t *boot_image_get_embdata(void) { return &g_bootloader_image; }

View File

@ -65,11 +65,11 @@ secbool boot_ucb_read(boot_ucb_t* ucb) {
uint32_t max_address = NONBOARDLOADER_START + NONBOARDLOADER_MAXSIZE;
if (ucb->header_address < min_address ||
ucb->header_address > max_address - sizeof(boot_header_t)) {
ucb->header_address > max_address - sizeof(boot_header_auth_t)) {
return secfalse;
}
const boot_header_t* hdr = (boot_header_t*)ucb->header_address;
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
@ -116,7 +116,7 @@ secbool boot_ucb_write(uint32_t header_address, uint32_t code_address) {
};
// Calculate the hash of the header
boot_header_t* hdr = (boot_header_t*)header_address;
boot_header_auth_t* hdr = (boot_header_auth_t*)header_address;
IMAGE_HASH_CTX ctx;
IMAGE_HASH_INIT(&ctx);

View File

@ -53,6 +53,10 @@ typedef struct {
/**
* Authenticated part of the boot header
*
* This structure can be extended in future versions if needed.
* Just make sure to add new fields at the end of the structure.
* Never remove or reorder existing fields.
*/
typedef struct __attribute__((packed)) {
/** Magic constant 'TRZQ' */
@ -96,7 +100,7 @@ typedef struct __attribute__((packed)) {
* the authenticated part of the header is maximized. */
uint8_t padding[0];
} boot_header_t;
} boot_header_auth_t;
/**
* Merkle proof structure used in the boot header to calculate the root
@ -147,7 +151,7 @@ typedef struct __attribute__((packed)) {
* @param address Address of the boot header in flash memory
* @return Pointer to the boot header if valid, NULL otherwise.
*/
const boot_header_t* boot_header_check_integrity(uint32_t address);
const boot_header_auth_t* boot_header_auth_get(uint32_t address);
/**
* Gets pointer to the unauthenticated part of the boot header.
@ -169,7 +173,7 @@ const boot_header_unauth_t* boot_header_unauth_get(
* @param code_address Address of the bootloader code in flash memory
* @param root Pointer to the output Merkle root node
*/
void boot_header_calc_merkle_root(const boot_header_t* hdr,
void boot_header_calc_merkle_root(const boot_header_auth_t* hdr,
uint32_t code_address,
merkle_proof_node_t* root);
@ -184,7 +188,7 @@ void boot_header_calc_merkle_root(const boot_header_t* hdr,
* @param merkle_root Pointer to the Merkle root
* @return secbool indicating whether the signature verification was successful.
*/
secbool boot_header_check_signature(const boot_header_t* hdr,
secbool boot_header_check_signature(const boot_header_auth_t* hdr,
const merkle_proof_node_t* merkle_root);
/**
@ -194,7 +198,7 @@ secbool boot_header_check_signature(const boot_header_t* hdr,
*
* @param hdr Pointer to the new boot header
* @param code_address Address of the new bootloader code in flash memory
* @return secbool indicating whether the boot header and code are unchanged
* @return secbool indicating whether the boot header and code need update
*/
secbool bootloader_is_unchanged(const boot_header_t* hdr,
uint32_t code_address);
secbool bootloader_area_needs_update(const boot_header_auth_t* hdr,
uint32_t code_address);

View File

@ -27,9 +27,10 @@
typedef struct {
const void* image_ptr;
size_t image_size;
#ifndef USE_BOOT_UCB
uint8_t hash_00[32];
uint8_t hash_FF[32];
#endif
} boot_image_t;
/**

View File

@ -16,11 +16,11 @@ def no_echo(*args, **kwargs):
"-D", "--sign-dev-keys", is_flag=True, help="Sign with development header keys."
)
@click.option(
"-d",
"--digest",
"print_digest",
"-m",
"--merkle-root",
"print_merkle_root",
is_flag=True,
help="Only output header digest for signing and exit.",
help="Only output Merkle root for signing and exit.",
)
@click.option(
"-M",
@ -37,7 +37,7 @@ def cli(
dry_run,
sign_dev_keys,
merkle_proof,
print_digest,
print_merkle_root,
quiet,
):
"""Manage firmware headers.
@ -64,9 +64,10 @@ def cli(
f"Could not parse file (magic bytes: {magic})"
) from e
digest = fw.digest()
if print_digest:
click.echo(digest.hex())
fw.set_merkle_proof(list(map(bytes.fromhex, merkle_proof)))
if print_merkle_root:
click.echo(fw.merkle_root().hex())
return
if quiet:
@ -74,9 +75,6 @@ def cli(
else:
echo = click.echo
# Add 16 items
fw.set_merkle_proof(list(map(bytes.fromhex, merkle_proof)))
echo("Signing with dev keys...", err=True)
fw.sign_with_devkeys()

View File

@ -477,7 +477,7 @@ class BootloaderV2Image(firmware.BootableImage):
# digest is calculated from
self.header.sigmask = (1 << 0) | (1 << 1)
digest = self.digest()
digest = self.merkle_root()
# SLH signature signs the image digest
for idx, key in enumerate(self.DEV_PRIVATE_PQ_KEYS):
@ -504,13 +504,14 @@ class BootloaderV2Image(firmware.BootableImage):
output = [
"Firmware Header " + _format_container(header_out),
f"Fingerprint: {click.style(self.digest().hex(), bold=True)}",
f"Leaf hash: {click.style(self.leaf_hash().hex(), bold=True)}",
f"Merkle root: {click.style(self.merkle_root().hex(), bold=True)}",
]
return "\n".join(output)
def verify(self, dev_keys: bool = False) -> None:
digest = self.digest()
digest = self.merkle_root()
for idx, key in enumerate(self.public_ec_keys(dev_keys)):
if not _ed25519.checkvalid(self.unauth.ec_signatures[idx], digest, key):

View File

@ -245,7 +245,9 @@ class BootHeader(Struct):
"version" / TupleAdapter(c.Int8ul, c.Int8ul, c.Int8ul, c.Int8ul),
"fix_version" / TupleAdapter(c.Int8ul, c.Int8ul, c.Int8ul, c.Int8ul),
"min_prev_version" / TupleAdapter(c.Int8ul, c.Int8ul, c.Int8ul, c.Int8ul),
"monotonic" / c.Int32ul,
"monotonic" / c.Int8ul,
"sigmask" / c.Int8ul,
"_reserved" / c.Padding(2),
"header_len" / c.Int32ul,
"auth_len" / c.Int32ul,
"code_length" / c.Rebuild(
@ -255,7 +257,6 @@ class BootHeader(Struct):
else (this.code_length or 0)
),
"storage_address" / c.Int32ul,
"sigmask" / c.Int32ul,
"firmware_root" / c.Bytes(32),
# Variable-length padding that's part of the authenticated header
@ -319,15 +320,28 @@ class BootableImage(Struct):
self.unauth.merkle_proof = proof
self.header.auth_len = self.header.header_len - len(self.unauth.build())
def digest(self) -> bytes:
hash_params = self.get_hash_params()
hash_fn = hash_params.hash_function
def _leaf_value(self) -> bytes:
hash_fn = self.get_hash_params().hash_function
assert hash_fn is hashlib.sha256 # currently hardcoded in trezorlib.merkle_tree
auth_header = self.header.build()
code_hash = hash_fn(self.code).digest()
leaf = auth_header + code_hash
return merkle_tree.evaluate_proof(leaf, self.unauth.merkle_proof)
return auth_header + code_hash
def leaf_hash(self) -> bytes:
"""Calculate the Merkle leaf hash.
This is a fingerprint of _this particular_ boot header, which is not affected
by other members of the Merkle tree.
"""
return merkle_tree.leaf_hash(self._leaf_value())
def merkle_root(self) -> bytes:
"""Calculate the Merkle root hash.
It identifies the entire Merkle tree that contains this boot header. Signatures
are evaluated over this value.
"""
return merkle_tree.evaluate_proof(self._leaf_value(), self.unauth.merkle_proof)
def model(self) -> Model | None:
if isinstance(self.header.hw_model, Model):