fix(core): assert the monotonic versions fit what monoctr can store
What changed, and why it matters
This commit adds build-time safety checks to prevent future Trezor firmware versions from accidentally setting anti-rollback version numbers too high for the hardware storage to handle. If a version were set above the storage limit, the device could either wrongly refuse to boot a valid update, or silently stop enforcing downgrade protection. The fix prevents that mistake at compile time, before any device is shipped. It does not fix an active bug today because all current models are well below the limit.
No urgent action needed; this is a defensive hardening change. Ensure future model version bumps are still validated by these assertions during CI builds, and consider documenting MONOCTR_MAX_VALUE in model version definitions so developers remain aware of the limit.
Security signals we found
anti-rollback/downgrade protection
build-time static assertion
monotonic counter storage limit
silent failure mode prevention
bootloader version verification
Evidence from the diff
The bootloader’s monoctr counter storage encodes values as unary (N = N 16-byte blocks), so the effective maximum is MONOCTR_MAX_VALUE (63), not the uint8_t transport width. Previously, a model defining BOOTLOADER_MONOTONIC_VERSION, FIRMWARE_MONOTONIC_VERSION, or SECMON_MONOTONIC_VERSION above 63 would compile successfully but fail at runtime: bootloader floor comparison would reject a good image as ‘BOOTLOADER DOWNGRADED’, while firmware/secmon floors would silently stop advancing, weakening anti-rollback. The patch adds _Static_assert checks in version_check.c, the only translation unit that sees all three constants and owns the monoctr policy, so the build fails immediately if any model exceeds the limit. SECMON_MONOTONIC_VERSION is conditionally checked because only four of nine models define it.
Changed components
core/embed/projects/bootloader/version_check.cmonoctr counter subsystemanti-rollback version enforcementInspect captured patch +15 / −0
### core/embed/projects/bootloader/version_check.c
@@ -23,6 +23,21 @@
#include "model_version.h"
#include "version_check.h"
+// The way monoctr encodes its counters, not the uint8_t the value travels in,
+// is what caps them at MONOCTR_MAX_VALUE. monoctr_write rejects anything above
+// that, so at runtime the version would simply fail to be stored -- and the
+// read-back checks below would then fail the ensure() and halt the device with
+// a downgrade-protection error on a perfectly good image. Catch it here
+// instead, at build time, for every model.
+_Static_assert(BOOTLOADER_MONOTONIC_VERSION <= MONOCTR_MAX_VALUE,
+ "BOOTLOADER_MONOTONIC_VERSION exceeds what monoctr can store");
+_Static_assert(FIRMWARE_MONOTONIC_VERSION <= MONOCTR_MAX_VALUE,
+ "FIRMWARE_MONOTONIC_VERSION exceeds what monoctr can store");
+#ifdef SECMON_MONOTONIC_VERSION // only models with a secmon define it
+_Static_assert(SECMON_MONOTONIC_VERSION <= MONOCTR_MAX_VALUE,
+ "SECMON_MONOTONIC_VERSION exceeds what monoctr can store");
+#endif
+
void ensure_bootloader_min_version(void) {
monoctr_write(MONOCTR_BOOTLOADER_VERSION, BOOTLOADER_MONOTONIC_VERSION);
uint8_t val = 0;Why this scored 34/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.