diff --git a/core/SConscript.bootloader b/core/SConscript.bootloader
index dd0060af83..e5226cd313 100644
--- a/core/SConscript.bootloader
+++ b/core/SConscript.bootloader
@@ -168,6 +168,7 @@ SOURCE_BOOTLOADER = [
'embed/bootloader/main.c',
'embed/bootloader/messages.c',
'embed/bootloader/protob/messages.pb.c',
+ 'embed/bootloader/version_check.c',
]
diff --git a/core/SConscript.bootloader_emu b/core/SConscript.bootloader_emu
index 009415fcf8..9d0627cbcc 100644
--- a/core/SConscript.bootloader_emu
+++ b/core/SConscript.bootloader_emu
@@ -140,6 +140,7 @@ SOURCE_BOOTLOADER = [
'embed/bootloader/main.c',
'embed/bootloader/messages.c',
'embed/bootloader/emulator.c',
+ 'embed/bootloader/version_check.c',
'embed/bootloader/protob/messages.pb.c',
]
diff --git a/core/embed/bootloader/.changelog.d/4133.added b/core/embed/bootloader/.changelog.d/4133.added
new file mode 100644
index 0000000000..b808dcad60
--- /dev/null
+++ b/core/embed/bootloader/.changelog.d/4133.added
@@ -0,0 +1 @@
+Added firmware downgrade protection
diff --git a/core/embed/bootloader/main.c b/core/embed/bootloader/main.c
index 23d0ee9133..d93ec0cf4a 100644
--- a/core/embed/bootloader/main.c
+++ b/core/embed/bootloader/main.c
@@ -72,6 +72,7 @@
#include "monoctr.h"
#include "rust_ui.h"
#include "unit_variant.h"
+#include "version_check.h"
#ifdef TREZOR_EMULATOR
#include "emulator.h"
@@ -261,22 +262,6 @@ static secbool check_vendor_header_lock(const vendor_header *const vhdr) {
return sectrue * (0 == memcmp(lock, hash, 32));
}
-// protection against bootloader downgrade
-
-#if PRODUCTION && !defined STM32U5
-
-static void check_bootloader_version(void) {
- ensure(
- monoctr_write(MONOCTR_BOOTLOADER_VERSION, BOOTLOADER_MONOTONIC_VERSION),
- NULL);
- uint8_t val = 0;
- ensure(monoctr_read(MONOCTR_BOOTLOADER_VERSION, &val), NULL);
- ensure(sectrue * (val == BOOTLOADER_MONOTONIC_VERSION),
- "Bootloader downgrade protection");
-}
-
-#endif
-
void failed_jump_to_firmware(void) { error_shutdown("(glitch)"); }
void real_jump_to_firmware(void) {
@@ -304,6 +289,10 @@ void real_jump_to_firmware(void) {
ensure(check_image_header_sig(hdr, vhdr.vsig_m, vhdr.vsig_n, vhdr.vpub),
"Firmware is corrupted");
+ ensure(check_firmware_min_version(hdr->version),
+ "Firmware downgrade protection");
+ ensure_firmware_min_version(hdr->version);
+
ensure(check_image_contents(hdr, IMAGE_HEADER_SIZE + vhdr.hdrlen,
&FIRMWARE_AREA),
"Firmware is corrupted");
@@ -422,6 +411,8 @@ int bootloader_main(void) {
volatile secbool vhdr_lock_ok = secfalse;
volatile secbool img_hdr_ok = secfalse;
volatile secbool model_ok = secfalse;
+ volatile secbool signatures_ok = secfalse;
+ volatile secbool version_ok = secfalse;
volatile secbool header_present = secfalse;
volatile secbool firmware_present = secfalse;
volatile secbool firmware_present_backup = secfalse;
@@ -448,12 +439,22 @@ int bootloader_main(void) {
if (sectrue == img_hdr_ok) {
model_ok = check_image_model(hdr);
}
+
if (sectrue == model_ok) {
- header_present =
+ signatures_ok =
check_image_header_sig(hdr, vhdr.vsig_m, vhdr.vsig_n, vhdr.vpub);
}
+ if (sectrue == signatures_ok) {
+ version_ok = check_firmware_min_version(hdr->monotonic);
+ }
+
+ if (sectrue == version_ok) {
+ header_present = version_ok;
+ }
+
if (sectrue == header_present) {
+ ensure_firmware_min_version(hdr->monotonic);
firmware_present = check_image_contents(
hdr, IMAGE_HEADER_SIZE + vhdr.hdrlen, &FIRMWARE_AREA);
firmware_present_backup = firmware_present;
@@ -477,12 +478,12 @@ int bootloader_main(void) {
#if PRODUCTION && !defined STM32U5
// for STM32U5, this check is moved to boardloader
- check_bootloader_version();
+ ensure_bootloader_min_version();
#endif
switch (bootargs_get_command()) {
case BOOT_COMMAND_STOP_AND_WAIT:
- // firmare requested to stay in bootloader
+ // firmware requested to stay in bootloader
stay_in_bootloader = sectrue;
break;
case BOOT_COMMAND_INSTALL_UPGRADE:
diff --git a/core/embed/bootloader/messages.c b/core/embed/bootloader/messages.c
index 568cafdb26..5006afb856 100644
--- a/core/embed/bootloader/messages.c
+++ b/core/embed/bootloader/messages.c
@@ -37,6 +37,7 @@
#include "bootui.h"
#include "messages.h"
#include "rust_ui.h"
+#include "version_check.h"
#include "memzero.h"
#include "model.h"
@@ -472,6 +473,10 @@ static void detect_installation(const vendor_header *current_vhdr,
*is_new = sectrue;
return;
}
+ if (sectrue != check_firmware_min_version(current_hdr->monotonic)) {
+ *is_new = sectrue;
+ return;
+ }
if (sectrue != check_image_header_sig(current_hdr, current_vhdr->vsig_m,
current_vhdr->vsig_n,
current_vhdr->vpub)) {
@@ -574,6 +579,14 @@ int process_msg_FirmwareUpload(uint8_t iface_num, uint32_t msg_size,
return UPLOAD_ERR_INVALID_IMAGE_HEADER_SIG;
}
+ if (sectrue != check_firmware_min_version(received_hdr->monotonic)) {
+ MSG_SEND_INIT(Failure);
+ MSG_SEND_ASSIGN_VALUE(code, FailureType_Failure_ProcessError);
+ MSG_SEND_ASSIGN_STRING(message, "Cannot downgrade to this version");
+ MSG_SEND(Failure);
+ return UPLOAD_ERR_INVALID_IMAGE_HEADER_VERSION;
+ }
+
memcpy(&hdr, received_hdr, sizeof(hdr));
size_t headers_end = IMAGE_HEADER_SIZE + vhdr.hdrlen;
diff --git a/core/embed/bootloader/messages.h b/core/embed/bootloader/messages.h
index d1ff9a2f08..ad8e3f4211 100644
--- a/core/embed/bootloader/messages.h
+++ b/core/embed/bootloader/messages.h
@@ -39,6 +39,7 @@ enum {
UPLOAD_ERR_INVALID_IMAGE_HEADER = -4,
UPLOAD_ERR_INVALID_IMAGE_MODEL = -5,
UPLOAD_ERR_INVALID_IMAGE_HEADER_SIG = -6,
+ UPLOAD_ERR_INVALID_IMAGE_HEADER_VERSION = -16,
UPLOAD_ERR_USER_ABORT = -7,
UPLOAD_ERR_FIRMWARE_TOO_BIG = -8,
UPLOAD_ERR_INVALID_CHUNK_HASH = -9,
diff --git a/core/embed/bootloader/version_check.c b/core/embed/bootloader/version_check.c
new file mode 100644
index 0000000000..ae7d225154
--- /dev/null
+++ b/core/embed/bootloader/version_check.c
@@ -0,0 +1,48 @@
+/*
+ * 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 "version_check.h"
+
+#include "error_handling.h"
+#include "model_version.h"
+#include "monoctr.h"
+
+void ensure_bootloader_min_version(void) {
+ monoctr_write(MONOCTR_BOOTLOADER_VERSION, BOOTLOADER_MONOTONIC_VERSION);
+ uint8_t val = 0;
+ ensure(monoctr_read(MONOCTR_BOOTLOADER_VERSION, &val), NULL);
+ ensure(sectrue * (val == BOOTLOADER_MONOTONIC_VERSION),
+ "Bootloader downgrade protection");
+}
+
+secbool check_firmware_min_version(uint8_t check_version) {
+ uint8_t min_version = 0;
+ ensure(monoctr_read(MONOCTR_FIRMWARE_VERSION, &min_version), "monoctr read");
+
+ return (check_version >= min_version) * sectrue;
+}
+
+void ensure_firmware_min_version(uint8_t version) {
+ monoctr_write(MONOCTR_FIRMWARE_VERSION, version);
+ uint8_t val = 0;
+ ensure(monoctr_read(MONOCTR_FIRMWARE_VERSION, &val), NULL);
+ ensure(sectrue * (val == version), "Firmware downgrade protection");
+}
diff --git a/core/embed/bootloader/version_check.h b/core/embed/bootloader/version_check.h
new file mode 100644
index 0000000000..a7011808b5
--- /dev/null
+++ b/core/embed/bootloader/version_check.h
@@ -0,0 +1,40 @@
+/*
+ * 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 .
+ */
+
+#pragma once
+
+#include
+#include "secbool.h"
+
+// Protection against bootloader downgrade
+
+// Ensures bootloader version is stored in monotonic counter
+// If the version cannot be written, the function will shutdown the device
+void ensure_bootloader_min_version(void);
+
+// Protection against firmware downgrade
+
+// This functions checks if the firmware version is at least the minimum
+// required version, returns sectrue if check_version is higher or equal to the
+// stored version
+secbool check_firmware_min_version(uint8_t check_version);
+
+// Ensures firmware version is stored in monotonic counter
+// If the version cannot be written, the function will shutdown the device
+void ensure_firmware_min_version(uint8_t version);
diff --git a/core/embed/trezorhal/monoctr.h b/core/embed/trezorhal/monoctr.h
index 0d5e2bcfd1..296b573dbf 100644
--- a/core/embed/trezorhal/monoctr.h
+++ b/core/embed/trezorhal/monoctr.h
@@ -32,8 +32,12 @@ typedef enum {
MONOCTR_FIRMWARE_VERSION = 1,
} monoctr_type_t;
+// Write a new value to the monotonic counter
+// Returns sectrue on success, when value is lower than the current value
+// the write fails and returns secfalse
secbool monoctr_write(monoctr_type_t type, uint8_t value);
+// Read the current value of the monotonic counter
secbool monoctr_read(monoctr_type_t type, uint8_t* value);
#endif
diff --git a/core/embed/trezorhal/stm32f4/monoctr.c b/core/embed/trezorhal/stm32f4/monoctr.c
index a30e13d907..70b58faa9d 100644
--- a/core/embed/trezorhal/stm32f4/monoctr.c
+++ b/core/embed/trezorhal/stm32f4/monoctr.c
@@ -41,6 +41,26 @@ secbool monoctr_write(monoctr_type_t type, uint8_t value) {
return secfalse;
}
+ int block = get_otp_block(type);
+
+ if (block < 0) {
+ return secfalse;
+ }
+
+ uint8_t current_value = 0;
+
+ if (sectrue != monoctr_read(type, ¤t_value)) {
+ return secfalse;
+ }
+
+ if (value < current_value) {
+ return secfalse;
+ }
+
+ if (value == current_value) {
+ return sectrue;
+ }
+
uint8_t bits[FLASH_OTP_BLOCK_SIZE];
for (int i = 0; i < FLASH_OTP_BLOCK_SIZE * 8; i++) {
if (i < value) {
@@ -50,12 +70,6 @@ secbool monoctr_write(monoctr_type_t type, uint8_t value) {
}
}
- int block = get_otp_block(type);
-
- if (block < 0) {
- return secfalse;
- }
-
ensure(flash_otp_write(block, 0, bits, FLASH_OTP_BLOCK_SIZE), NULL);
#endif
diff --git a/core/embed/trezorhal/stm32u5/monoctr.c b/core/embed/trezorhal/stm32u5/monoctr.c
index e9db4eb273..6504371fa2 100644
--- a/core/embed/trezorhal/stm32u5/monoctr.c
+++ b/core/embed/trezorhal/stm32u5/monoctr.c
@@ -44,6 +44,20 @@ secbool monoctr_write(monoctr_type_t type, uint8_t value) {
return secfalse;
}
+ uint8_t current_value = 0;
+
+ if (sectrue != monoctr_read(type, ¤t_value)) {
+ return secfalse;
+ }
+
+ if (value < current_value) {
+ return secfalse;
+ }
+
+ if (value == current_value) {
+ return sectrue;
+ }
+
for (int i = 0; i < value; i++) {
uint32_t data[4] = {0};
secret_write((uint8_t *)data, offset + i * 16, 16);
diff --git a/core/embed/trezorhal/unix/monoctr.c b/core/embed/trezorhal/unix/monoctr.c
index e1ddf7a358..a8a140db60 100644
--- a/core/embed/trezorhal/unix/monoctr.c
+++ b/core/embed/trezorhal/unix/monoctr.c
@@ -38,6 +38,26 @@ secbool monoctr_write(monoctr_type_t type, uint8_t value) {
return secfalse;
}
+ int block = get_otp_block(type);
+
+ if (block < 0) {
+ return secfalse;
+ }
+
+ uint8_t current_value = 0;
+
+ if (sectrue != monoctr_read(type, ¤t_value)) {
+ return secfalse;
+ }
+
+ if (value < current_value) {
+ return secfalse;
+ }
+
+ if (value == current_value) {
+ return sectrue;
+ }
+
uint8_t bits[FLASH_OTP_BLOCK_SIZE];
for (int i = 0; i < FLASH_OTP_BLOCK_SIZE * 8; i++) {
if (i < value) {
@@ -47,12 +67,6 @@ secbool monoctr_write(monoctr_type_t type, uint8_t value) {
}
}
- int block = get_otp_block(type);
-
- if (block < 0) {
- return secfalse;
- }
-
ensure(flash_otp_write(block, 0, bits, FLASH_OTP_BLOCK_SIZE), NULL);
return sectrue;