fix(emulator): initialize mono counters before running bootemu
What changed, and why it matters
This commit fixes how the Trezor emulator initializes its 'monotonic counters'—security counters that only ever increase and are used to prevent downgrades to older, possibly vulnerable firmware. Before this fix, the emulator did not explicitly initialize these counters before running the boot emulator, which could lead to uninitialized or incorrect counter values during emulator-based testing. The fix adds a dedicated initialization routine and calls it in every relevant firmware component. On real hardware the initialization is currently a no-op, so this primarily affects the Unix/emulator build used for development and testing.
Treat as a low-to-moderate hardening fix for the emulator/test environment. Verify that `monoctr_init()` is invoked in all new entry points added in future firmware components. Review whether the STM32 no-op implementations should eventually perform real initialization to match the emulator behavior. No urgent user action is indicated for production hardware based solely on this diff.
Security signals we found
Adds explicit initialization of anti-rollback monotonic counters before boot emulator runs
Prevents use of uninitialized monotonic counter state in emulator builds
Touches bootloader, kernel, secmon, and boardloader entry points—core trust-boundary code
Real hardware implementations are no-ops, limiting direct hardware impact
Includes a temporary workaround OTP block mapping for SECMON counter on Unix
Evidence from the diff
The change introduces monoctr_init() in sec/monoctr.h and provides implementations for STM32F4, STM32U5, and Unix targets. On STM32 targets the function is empty (a no-op). On the Unix emulator target it explicitly writes 0 to the bootloader, firmware, and (when applicable) secure-monitor monotonic counters. The commit then adds monoctr_init() calls at the start of drivers_init() or boot_sequence() in boardloader, bootloader, bootloader_ci, kernel, prodtest, secmon, and the Unix main build. The header is also updated with Doxygen comments and a new MONOCTR_SECMON_VERSION counter type. The Unix implementation adds a temporary OTP block mapping for the SECMON counter (block 31) as a workaround.
Changed components
core/embed/sec/monoctr (all targets)core/embed/projects/boardloader/main.ccore/embed/projects/bootloader/main.ccore/embed/projects/bootloader/emulator.ccore/embed/projects/bootloader_ci/main.ccore/embed/projects/kernel/main.ccore/embed/projects/prodtest/main.ccore/embed/projects/secmon/main.ccore/embed/projects/unix/main_main.cInspect captured patch +68 / −4
### core/embed/projects/boardloader/main.c
@@ -23,6 +23,7 @@
#include <io/display.h>
#include <io/rsod.h>
#include <sec/board_capabilities.h>
+#include <sec/monoctr.h>
#include <sec/option_bytes.h>
#include <sec/rsod_special.h>
#include <sys/bootutils.h>
@@ -72,6 +73,8 @@
#endif
static void drivers_init(void) {
+ monoctr_init();
+
#ifdef USE_PMIC
pmic_init();
#endif
### core/embed/projects/bootloader/emulator.c
@@ -8,6 +8,7 @@
#include <SDL3/SDL.h>
#include <io/display.h>
+#include <sec/monoctr.h>
#include <sys/bootargs.h>
#include <sys/bootutils.h>
#include <sys/flash.h>
### core/embed/projects/bootloader/main.c
@@ -26,6 +26,7 @@
#include <io/rsod.h>
#include <io/usb_config.h>
#include <sec/image.h>
+#include <sec/monoctr.h>
#include <sec/random_delays.h>
#include <sec/rsod_special.h>
#include <sec/unit_properties.h>
@@ -162,6 +163,8 @@ static void display_touch_init(secbool manufacturing_mode,
static secbool boot_sequence(void) {
secbool stay_in_bootloader = secfalse;
+ monoctr_init();
+
#ifdef USE_SECRET
secret_init();
#endif
### core/embed/projects/bootloader_ci/main.c
@@ -28,6 +28,7 @@
#include <io/usb.h>
#include <io/usb_config.h>
#include <sec/image.h>
+#include <sec/monoctr.h>
#include <sec/random_delays.h>
#include <sec/rsod_special.h>
#include <sys/bootargs.h>
@@ -60,6 +61,8 @@
#define USB_IFACE_NUM SYSHANDLE_USB_WIRE
static void drivers_init(void) {
+ monoctr_init();
+
display_init(DISPLAY_RESET_CONTENT);
random_delays_init();
### core/embed/projects/kernel/main.c
@@ -25,6 +25,7 @@
#include <io/rsod.h>
#include <sec/board_capabilities.h>
#include <sec/boot_image.h>
+#include <sec/monoctr.h>
#include <sec/option_bytes.h>
#include <sec/random_delays.h>
#include <sec/unit_properties.h>
@@ -124,6 +125,8 @@ void drivers_init() {
UNUSED(status);
#ifdef SECURE_MODE
+ monoctr_init();
+
parse_boardloader_capabilities();
unit_properties_init();
### core/embed/projects/prodtest/main.c
@@ -28,6 +28,7 @@
#include <io/usb_config.h>
#include <rtl/cli.h>
#include <sec/board_capabilities.h>
+#include <sec/monoctr.h>
#include <sec/rsod_special.h>
#include <sec/unit_properties.h>
#include <sys/flash_otp.h>
@@ -149,6 +150,7 @@ static bool g_rgbled_control_disabled = false;
void prodtest_disable_rgbled_control(void) { g_rgbled_control_disabled = true; }
static void drivers_init(void) {
+ monoctr_init();
parse_boardloader_capabilities();
unit_properties_init();
### core/embed/projects/secmon/main.c
@@ -22,6 +22,7 @@
#include <sec/board_capabilities.h>
#include <sec/boot_image.h>
+#include <sec/monoctr.h>
#include <sec/option_bytes.h>
#include <sec/random_delays.h>
#include <sec/secure_aes.h>
@@ -99,6 +100,8 @@ void usb_power_init(void) {
static void drivers_init(void) {
flash_init();
+ monoctr_init();
+
parse_boardloader_capabilities();
unit_properties_init();
### core/embed/projects/unix/main_main.c
@@ -24,6 +24,7 @@
#include <io/display.h>
#include <io/rsod.h>
#include <io/usb_config.h>
+#include <sec/monoctr.h>
#include <sec/rsod_special.h>
#include <sec/unit_properties.h>
#include <sys/applet.h>
@@ -87,6 +88,8 @@ static void drivers_init(void) {
flash_init();
flash_otp_init();
+ monoctr_init();
+
unit_properties_init();
display_init(DISPLAY_RESET_CONTENT);
### core/embed/sec/monoctr/inc/sec/monoctr.h
@@ -27,18 +27,43 @@
#define MONOCTR_MAX_VALUE 63
+/**
+ * @brief Enum representing the types of available monotonic counters.
+ */
typedef enum {
MONOCTR_BOOTLOADER_VERSION = 0,
MONOCTR_FIRMWARE_VERSION = 1,
MONOCTR_SECMON_VERSION = 2,
} 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
+/**
+ * @brief Initializes the monotonic counter module.
+ *
+ * This function should be called before any other operations on the monotonic
+ * counter.
+ */
+void monoctr_init(void);
+
+/**
+ * @brief Write a new value to the monotonic counter
+ *
+ * @param type The type of the monotonic counter to write to.
+ * @param value The new value to write to the monotonic counter.
+ * Maximum value is defined by MONOCTR_MAX_VALUE.
+ *
+ * @return Returns sectrue on success when value is not lower than the current
+ * value. If the write fails, returns secfalse.
+ * */
secbool monoctr_write(monoctr_type_t type, uint8_t value);
-// Read the current value of the monotonic counter
+/**
+ * @brief Read the current value of the monotonic counter
+ *
+ * @param type The type of the monotonic counter to read from.
+ * @param value Pointer to store the current value of the monotonic counter.
+ *
+ * @return Returns sectrue on success, or secfalse if the read fails.
+ */
secbool monoctr_read(monoctr_type_t type, uint8_t* value);
#endif // SECURE_MODE
### core/embed/sec/monoctr/stm32f4/monoctr.c
@@ -32,6 +32,8 @@
static uint8_t dummy_version = 0;
#endif
+void monoctr_init(void) {}
+
#if PRODUCTION
static int get_otp_block(monoctr_type_t type) {
switch (type) {
### core/embed/sec/monoctr/stm32u5/monoctr.c
@@ -26,6 +26,8 @@
#include <sys/flash.h>
#include <sys/mpu.h>
+void monoctr_init(void) {}
+
static int32_t get_offset(monoctr_type_t type) {
switch (type) {
case MONOCTR_BOOTLOADER_VERSION:
### core/embed/sec/monoctr/unix/monoctr.c
@@ -25,12 +25,26 @@
#include <sec/monoctr.h>
#include <sys/flash_otp.h>
+void monoctr_init(void) {
+ ensure(monoctr_write(MONOCTR_BOOTLOADER_VERSION, 0),
+ "initialize bootloader monotonic counter");
+ ensure(monoctr_write(MONOCTR_FIRMWARE_VERSION, 0),
+ "initialize firmware monotonic counter");
+#ifdef USE_SECMON_VERIFICATION
+ ensure(monoctr_write(MONOCTR_SECMON_VERSION, 0),
+ "initialize secure-monitor monotonic counter");
+#endif
+}
+
static int get_otp_block(monoctr_type_t type) {
switch (type) {
case MONOCTR_BOOTLOADER_VERSION:
return FLASH_OTP_BLOCK_BOOTLOADER_VERSION;
case MONOCTR_FIRMWARE_VERSION:
return FLASH_OTP_BLOCK_FIRMWARE_VERSION;
+ case MONOCTR_SECMON_VERSION:
+ return 31; // Workaround until we implement STM32U5 monoctr emulator
+ // properly
default:
return -1;
}Why this scored 35/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.