feat(core): introduce runtime TRNG use checks
What changed, and why it matters
This commit adds runtime bookkeeping so the firmware can verify that every strong random-number generation actually used all available hardware random sources (the main chip, and optional Optiga/Tropic chips). It also fixes a small edge case in how Optiga random data is requested. The change is defensive: it makes the device deliberately fail if an expected entropy source was somehow skipped, reducing the risk that a bug or tampered code path silently weakens randomness used for secrets.
Treat as a hardening/defensive commit rather than an active vulnerability fix. Review that `rng_fill_buffer_strong()` is invoked for every security-critical random draw and that the new `ensure_true()` assertions cannot be bypassed by fault injection. Verify the Optiga chunking fix against the peripheral's minimum-size requirements in production tests.
Security signals we found
Adds runtime assertions that all configured hardware entropy sources contributed to strong random output
Prevents emulator/mock PRNG from claiming real TRNG use via compile-time and runtime guards
Fixes an off-by-one/edge-case bug in Optiga random buffer chunking
Uses fatal `ensure_true()` checks, so a missing entropy source halts the device rather than silently proceeding
No changelog entry and no explicit security framing in commit message
Evidence from the diff
The patch introduces rng_use_flags tracking: each real TRNG implementation (STM32 MCU, Optiga, Tropic) calls rng_use_flag_set() after producing random bytes. rng_fill_buffer_strong() now clears flags at the start, mixes entropy from available sources, and then uses ensure_true() to assert that every configured source’s flag was set. Emulator builds intentionally cannot set the flag and rng_use_flag_is_set() always returns true there. A secondary fix in optiga_random_buffer() corrects chunking logic so it no longer requests an undersized block when size equals the minimum and handles leftover bytes more cleanly.
Changed components
core/embed/sys/rng/stm32/rng.ccore/embed/sys/rng/stm32/rng_use_flags.ccore/embed/sys/rng/unix/rng_use_flags.ccore/embed/sys/rng/inc/sys/rng_use_flags.hcore/embed/sys/rng/build.rscore/embed/sec/rng/rng_strong.ccore/embed/sec/optiga/optiga.ccore/embed/sec/tropic/tropic.cInspect captured patch +180 / −18
### core/embed/sec/optiga/optiga.c
@@ -28,6 +28,7 @@
#include <sec/rng_strong.h>
#include <sec/secret_keys.h>
#include <sec/storage.h>
+#include <sys/rng_use_flags.h>
#include "ecdsa.h"
#include "hash_to_curve.h"
#include "hmac.h"
@@ -256,23 +257,26 @@ void optiga_set_sec_max(void) {
}
bool optiga_random_buffer(uint8_t *dest, size_t size) {
- while (size > OPTIGA_RANDOM_MAX_SIZE) {
- if (optiga_get_random(dest, OPTIGA_RANDOM_MAX_SIZE) != OPTIGA_SUCCESS) {
+ while (size >= OPTIGA_RANDOM_MIN_SIZE) {
+ size_t chunk = MIN(size, OPTIGA_RANDOM_MAX_SIZE);
+ if (optiga_get_random(dest, chunk) != OPTIGA_SUCCESS) {
return false;
}
- dest += OPTIGA_RANDOM_MAX_SIZE;
- size -= OPTIGA_RANDOM_MAX_SIZE;
+ dest += chunk;
+ size -= chunk;
}
- if (size < OPTIGA_RANDOM_MIN_SIZE) {
+ if (size > 0) {
uint8_t buffer[OPTIGA_RANDOM_MIN_SIZE] = {0};
- optiga_result ret = optiga_get_random(buffer, OPTIGA_RANDOM_MIN_SIZE);
+ if (optiga_get_random(buffer, OPTIGA_RANDOM_MIN_SIZE) != OPTIGA_SUCCESS) {
+ return false;
+ }
memcpy(dest, buffer, size);
memzero(buffer, sizeof(buffer));
- return ret == OPTIGA_SUCCESS;
}
- return optiga_get_random(dest, size) == OPTIGA_SUCCESS;
+ rng_use_flag_set(RNG_TYPE_OPTIGA);
+ return true;
}
void optiga_random_buffer_time(uint32_t *time_ms) {
### core/embed/sec/rng/rng_strong.c
@@ -20,6 +20,7 @@
#include <trezor_rtl.h>
#include <sec/rng_strong.h>
+#include <sys/rng_use_flags.h>
#ifdef SECURE_MODE
@@ -34,16 +35,17 @@
#include "memzero.h"
#include "rand.h"
-#if defined(USE_OPTIGA) || defined(USE_TROPIC)
void rng_fill_buffer_strong(void* buffer, size_t buffer_size) {
+ rng_use_flags_clear();
+
rng_fill_buffer(buffer, buffer_size);
+#if defined(USE_OPTIGA) || defined(USE_TROPIC)
uint8_t* dst = (uint8_t*)buffer;
size_t remaining = buffer_size;
- uint8_t block[32] = {0};
-
while (remaining > 0) {
+ uint8_t block[32] = {0};
size_t block_size = MIN(remaining, sizeof(block));
// A failed entropy source halts the device with a fatal error to ensure
// that the error cannot be accidentally ignored.
@@ -68,13 +70,18 @@ void rng_fill_buffer_strong(void* buffer, size_t buffer_size) {
dst += block_size;
remaining -= block_size;
}
-}
+#endif // defined(USE_OPTIGA) || defined(USE_TROPIC)
-#else // defined(USE_OPTIGA) || defined(USE_TROPIC)
-void rng_fill_buffer_strong(void* buffer, size_t buffer_size) {
- rng_fill_buffer(buffer, buffer_size);
-}
+ ensure_true(rng_use_flag_is_set(RNG_TYPE_MCU), "MCU entropy source not used");
+#ifdef USE_OPTIGA
+ ensure_true(rng_use_flag_is_set(RNG_TYPE_OPTIGA),
+ "Optiga entropy source not used");
#endif
+#ifdef USE_TROPIC
+ ensure_true(rng_use_flag_is_set(RNG_TYPE_TROPIC),
+ "Tropic entropy source not used");
+#endif
+}
void rng_fill_buffer_strong_time(uint32_t* time_ms) {
// Assuming the buffer size is 32 bytes
### core/embed/sec/tropic/tropic.c
@@ -23,6 +23,7 @@
#include <sec/rng_strong.h>
#include <sec/secret_keys.h>
#include <sec/tropic.h>
+#include <sys/rng_use_flags.h>
#include <sys/systick.h>
#include "bignum.h"
@@ -1187,6 +1188,8 @@ bool tropic_random_buffer(void *buffer, size_t length) {
remaining -= chunk;
}
+ rng_use_flag_set(RNG_TYPE_TROPIC);
+
return true;
}
### core/embed/sys/rng/build.rs
@@ -6,9 +6,13 @@ pub fn def_module(lib: &mut CLibrary) -> Result<()> {
if cfg!(feature = "emulator") {
lib.add_define("USE_INSECURE_PRNG", Some("1"));
- lib.add_sources(["rng/unix/rng.c", "rng/unix/rng_mock.c"]);
+ lib.add_sources([
+ "rng/unix/rng.c",
+ "rng/unix/rng_use_flags.c",
+ "rng/unix/rng_mock.c",
+ ]);
} else if cfg!(feature = "mcu_stm32") {
- lib.add_source("rng/stm32/rng.c");
+ lib.add_sources(["rng/stm32/rng.c", "rng/stm32/rng_use_flags.c"]);
} else {
bail_unsupported!();
}
### core/embed/sys/rng/inc/sys/rng_use_flags.h
@@ -0,0 +1,60 @@
+/*
+ * 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/>.
+ */
+
+#pragma once
+
+#include <trezor_types.h>
+
+// This module records which real TRNG implementations were used, allowing
+// callers to verify that strong random numbers were generated using all TRNGs.
+
+/**
+ * @brief TRNG types.
+ */
+typedef enum {
+ RNG_TYPE_MCU,
+ RNG_TYPE_OPTIGA,
+ RNG_TYPE_TROPIC,
+} rng_type_t;
+
+/**
+ * @brief Clears all RNG flags.
+ *
+ * This function resets the internal state of the RNG flags, indicating that no
+ * RNG types have been used or set.
+ */
+void rng_use_flags_clear(void);
+
+#ifndef TREZOR_EMULATOR
+/**
+ * @brief Marks the specified RNG type as used.
+ *
+ * @note This function is intentionally unavailable in emulator builds. Code
+ * using a PRNG must not call it; it may only be called by real TRNG
+ * implementations in hardware builds.
+ */
+void rng_use_flag_set(rng_type_t type);
+#endif
+
+/**
+ * @brief Reads the use flag state of the specified RNG type.
+ *
+ * @return true if the specified RNG type has been used
+ */
+bool rng_use_flag_is_set(rng_type_t type);
### core/embed/sys/rng/stm32/rng.c
@@ -24,6 +24,7 @@
#include <trezor_rtl.h>
#include <sys/rng.h>
+#include <sys/rng_use_flags.h>
#include "rand.h"
@@ -76,6 +77,8 @@ void rng_fill_buffer(void* buffer, size_t buffer_size) {
uint32_t r = rng_get_u32();
memcpy(dst, &r, remaining);
}
+
+ rng_use_flag_set(RNG_TYPE_MCU);
}
#endif // SECURE_MODE
### core/embed/sys/rng/stm32/rng_use_flags.c
@@ -0,0 +1,34 @@
+/*
+ * 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 <sys/rng_use_flags.h>
+
+static uint32_t g_rng_flags = 0;
+
+void rng_use_flags_clear(void) { g_rng_flags = 0; }
+
+void rng_use_flag_set(rng_type_t type) { g_rng_flags |= (1 << type); }
+
+bool rng_use_flag_is_set(rng_type_t type) {
+ return (g_rng_flags & (1 << type)) != 0;
+}
+
+#endif // SECURE_MODE
### core/embed/sys/rng/unix/rng_use_flags.c
@@ -0,0 +1,47 @@
+/*
+ * 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 <sys/rng_use_flags.h>
+
+#ifndef TREZOR_EMULATOR
+#error "Mock RNG must not be compiled into a non-emulator build"
+#endif
+
+_Static_assert(sizeof(void*) == 8,
+ "Mock RNG compiled for a 32-bit target -- device build?");
+
+#if !defined(__linux__) && !defined(__APPLE__) && !defined(_WIN32)
+#error "Insecure PRNG is not supported on this target"
+#endif
+
+#if __STDC_HOSTED__ == 0
+#error "Insecure PRNG must not be compiled for a freestanding target"
+#endif
+
+#ifdef USE_INSECURE_PRNG
+
+// This implementation intentionally differs from real hardware. The flag
+// cannot be set in emulator builds, ensuring that only real TRNG
+// implementations can report their use.
+
+void rng_use_flags_clear(void) {}
+
+bool rng_use_flag_is_set(rng_type_t type) { return true; }
+
+#endif // USE_INSECURE_PRNGWhy this scored 32/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.