feat: rsod upon failure to generate strong randomness
What changed, and why it matters
This commit changes how a Trezor hardware wallet handles failures when generating strong random numbers. Previously, the function could return 'false' when an entropy source failed, and callers might ignore that failure. Now the device deliberately halts (shows a fatal Red Screen of Death) if any entropy source fails. This is a defensive hardening change, not an active bug fix for a known exploit, but it removes a class of safety issues where weak or predictable randomness could be used by mistake.
Treat as a security hardening improvement. Review that ensure() halts are recoverable only via reboot and do not leak sensitive state on screen. Verify that optiga_random_buffer() and tropic_random_buffer() failures are truly unrecoverable and that the RSOD message does not expose internal details. No immediate patch required, but downstream callers should be audited for any remaining ignored RNG failures.
Security signals we found
Hardening: fail-closed behavior for entropy-source failures
API change removes boolean success/failure return from RNG function
Use of fatal ensure() macro to prevent silent use of weak randomness
Wide blast radius across security-critical subsystems (storage keys, PIN secrets, master key, wipe code salt, Optiga/Tropic pairing)
No changelog entry despite security-relevant behavioral change
Evidence from the diff
The patch converts rng_fill_buffer_strong() from a bool-returning function to a void function that calls ensure() on the result of optiga_random_buffer()/tropic_random_buffer(). If either secure-element entropy source fails, the device halts via the ensure() fatal-error macro. All call sites are updated to remove now-unreachable error-handling branches. The change propagates through syscall/smcall dispatchers, verifiers, stubs, storage, prodtest, secret_keys, and legacy/test shims.
Changed components
core/embed/sec/rng/rng_strong.ccore/embed/sec/rng/inc/sec/rng_strong.hcore/embed/sec/optiga/optiga.ccore/embed/sec/tropic/tropic.ccore/embed/sec/secret_keys/stm32f4/secret_keys.ccore/embed/sec/secret_keys/stm32u5/secret_keys.ccore/embed/sys/smcall/stm32/*core/embed/sys/syscall/stm32/*core/embed/upymod/modtrezorcrypto/modtrezorcrypto-random.hstorage/storage.clegacy/sec/rng_strong.hstorage/tests/c/sec/rng_strong.hInspect captured patch +43 / −76
### core/embed/projects/prodtest/cmd/prodtest_secrets.c
@@ -65,9 +65,7 @@ secbool set_random_secret(uint8_t slot, size_t length) {
goto cleanup;
}
- if (!rng_fill_buffer_strong(secret, sizeof(secret))) {
- goto cleanup;
- }
+ rng_fill_buffer_strong(secret, sizeof(secret));
if (secret_key_set(slot, secret, sizeof(secret)) != sectrue) {
goto cleanup;
### core/embed/sec/optiga/optiga.c
@@ -754,22 +754,16 @@ bool optiga_pin_set(
bool ret = true;
uint8_t hmac_stretching_secret[OPTIGA_PIN_SECRET_SIZE] = {0};
- if (!rng_fill_buffer_strong(hmac_stretching_secret,
- sizeof(hmac_stretching_secret))) {
- ret = false;
- goto end;
- }
+ rng_fill_buffer_strong(hmac_stretching_secret,
+ sizeof(hmac_stretching_secret));
for (int i = 0; i < STRETCHED_PIN_COUNT; i++) {
optiga_pin_stretch_hmac_offline(hmac_stretching_secret, stretched_pins[i]);
}
// Generate and store the counter-protected PIN secret.
uint8_t pin_secret[OPTIGA_PIN_SECRET_SIZE] = {0};
- if (!rng_fill_buffer_strong(pin_secret, sizeof(pin_secret))) {
- ret = false;
- goto end;
- }
+ rng_fill_buffer_strong(pin_secret, sizeof(pin_secret));
if (optiga_set_data_object(OID_PIN_SECRET, false, pin_secret,
sizeof(pin_secret)) != OPTIGA_SUCCESS) {
### core/embed/sec/rng/inc/sec/rng_strong.h
@@ -35,11 +35,13 @@
* The function requires that Optiga and/or Tropic to be initialized
* if they are enabled by USE_OPTIGA/USE_TROPIC.
*
+ * If any entropy source fails, the function halts the device with a
+ * fatal error instead of returning. This is to ensure that a failure
+ * to generate strong randomness cannot be accidentally overlooked.
+ *
* @param buffer Buffer to fill with random bytes.
* @param buffer_size Size of the buffer in bytes.
- *
- * @return True on success, false on failure.
*/
-bool __wur rng_fill_buffer_strong(void* buffer, size_t buffer_size);
+void rng_fill_buffer_strong(void* buffer, size_t buffer_size);
void rng_fill_buffer_strong_time(uint32_t* time_ms);
### core/embed/sec/rng/rng_strong.c
@@ -35,7 +35,7 @@
#include "rand.h"
#if defined(USE_OPTIGA) || defined(USE_TROPIC)
-bool rng_fill_buffer_strong(void* buffer, size_t buffer_size) {
+void rng_fill_buffer_strong(void* buffer, size_t buffer_size) {
rng_fill_buffer(buffer, buffer_size);
uint8_t* dst = (uint8_t*)buffer;
@@ -45,19 +45,19 @@ bool rng_fill_buffer_strong(void* buffer, size_t buffer_size) {
while (remaining > 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.
#ifdef USE_OPTIGA
- if (!optiga_random_buffer(block, block_size)) {
- return false;
- }
+ ensure(sectrue * optiga_random_buffer(block, block_size),
+ "Optiga entropy source failed");
for (size_t i = 0; i < block_size; i++) {
dst[i] ^= block[i];
}
#endif
#ifdef USE_TROPIC
- if (!tropic_random_buffer(block, block_size)) {
- return false;
- }
+ ensure(sectrue * tropic_random_buffer(block, block_size),
+ "Tropic entropy source failed");
for (size_t i = 0; i < block_size; i++) {
dst[i] ^= block[i];
@@ -68,13 +68,11 @@ bool rng_fill_buffer_strong(void* buffer, size_t buffer_size) {
}
memzero(block, sizeof(block));
- return true;
}
#else // defined(USE_OPTIGA) || defined(USE_TROPIC)
-bool rng_fill_buffer_strong(void* buffer, size_t buffer_size) {
+void rng_fill_buffer_strong(void* buffer, size_t buffer_size) {
rng_fill_buffer(buffer, buffer_size);
- return true;
}
#endif
### core/embed/sec/secret_keys/stm32f4/secret_keys.c
@@ -48,10 +48,7 @@ secbool secret_key_delegated_identity(uint16_t rotation_index,
secbool secret_key_master_key_get(secret_key_master_key_t* master_key) {
if (secfalse == flash_otp_is_locked(FLASH_OTP_BLOCK_MASTER_KEY)) {
uint8_t rnd_bytes[SECRET_KEY_MASTER_KEY_SIZE];
- if (!rng_fill_buffer_strong(rnd_bytes, SECRET_KEY_MASTER_KEY_SIZE)) {
- memzero(rnd_bytes, sizeof(rnd_bytes));
- return secfalse;
- }
+ rng_fill_buffer_strong(rnd_bytes, SECRET_KEY_MASTER_KEY_SIZE);
ensure(flash_otp_write(FLASH_OTP_BLOCK_MASTER_KEY, 0, rnd_bytes,
SECRET_KEY_MASTER_KEY_SIZE),
NULL);
### core/embed/sec/secret_keys/stm32u5/secret_keys.c
@@ -179,10 +179,7 @@ secbool secret_key_optiga_pairing(uint8_t dest[OPTIGA_PAIRING_SECRET_SIZE]) {
secbool secret_key_master_key_get(secret_key_master_key_t* master_key) {
if (secfalse == flash_otp_is_locked(FLASH_OTP_BLOCK_MASTER_KEY)) {
uint8_t rnd_bytes[SECRET_KEY_MASTER_KEY_SIZE];
- if (!rng_fill_buffer_strong(rnd_bytes, SECRET_KEY_MASTER_KEY_SIZE)) {
- memzero(rnd_bytes, sizeof(rnd_bytes));
- return secfalse;
- }
+ rng_fill_buffer_strong(rnd_bytes, SECRET_KEY_MASTER_KEY_SIZE);
ensure(flash_otp_write(FLASH_OTP_BLOCK_MASTER_KEY, 0, rnd_bytes,
SECRET_KEY_MASTER_KEY_SIZE),
NULL);
### core/embed/sec/tropic/tropic.c
@@ -1395,9 +1395,7 @@ bool tropic_pin_set(
goto cleanup;
}
- if (!rng_fill_buffer_strong(reset_key, TROPIC_MAC_AND_DESTROY_SIZE)) {
- goto cleanup;
- }
+ rng_fill_buffer_strong(reset_key, TROPIC_MAC_AND_DESTROY_SIZE);
if (!update_change_pin_counter()) {
goto cleanup;
### core/embed/sys/smcall/stm32/smcall_dispatch.c
@@ -360,7 +360,7 @@ __attribute((no_stack_protector)) void smcall_handler(uint32_t *args,
case SMCALL_RNG_FILL_BUFFER_STRONG: {
uint8_t *buffer = (uint8_t *)args[0];
size_t buffer_size = args[1];
- args[0] = rng_fill_buffer_strong__verified(buffer, buffer_size);
+ rng_fill_buffer_strong__verified(buffer, buffer_size);
} break;
case SMCALL_FIRMWARE_GET_VENDOR: {
### core/embed/sys/smcall/stm32/smcall_stubs.c
@@ -343,9 +343,8 @@ void rng_fill_buffer(void *buffer, size_t buffer_size) {
smcall_invoke2((uint32_t)buffer, buffer_size, SMCALL_RNG_FILL_BUFFER);
}
-bool rng_fill_buffer_strong(void *buffer, size_t buffer_size) {
- return (bool)smcall_invoke2((uint32_t)buffer, buffer_size,
- SMCALL_RNG_FILL_BUFFER_STRONG);
+void rng_fill_buffer_strong(void *buffer, size_t buffer_size) {
+ smcall_invoke2((uint32_t)buffer, buffer_size, SMCALL_RNG_FILL_BUFFER_STRONG);
}
// =============================================================================
### core/embed/sys/smcall/stm32/smcall_verifiers.c
@@ -450,16 +450,16 @@ void rng_fill_buffer__verified(void *buffer, size_t buffer_size) {
apptask_access_violation();
}
-bool rng_fill_buffer_strong__verified(void *buffer, size_t buffer_size) {
+void rng_fill_buffer_strong__verified(void *buffer, size_t buffer_size) {
if (!probe_write_access(buffer, buffer_size)) {
goto access_violation;
}
- return rng_fill_buffer_strong(buffer, buffer_size);
+ rng_fill_buffer_strong(buffer, buffer_size);
+ return;
access_violation:
apptask_access_violation();
- return false;
}
// ---------------------------------------------------------------------
### core/embed/sys/smcall/stm32/smcall_verifiers.h
@@ -125,7 +125,7 @@ secbool storage_next_counter__verified(const uint16_t key, uint32_t *count);
void rng_fill_buffer__verified(void *buffer, size_t buffer_size);
-bool rng_fill_buffer_strong__verified(void *buffer, size_t buffer_size);
+void rng_fill_buffer_strong__verified(void *buffer, size_t buffer_size);
// ---------------------------------------------------------------------
#include <sec/fwutils.h>
### core/embed/sys/syscall/stm32/syscall_dispatch.c
@@ -675,7 +675,7 @@ __attribute((no_stack_protector)) void syscall_handler(uint32_t *args,
case SYSCALL_RNG_FILL_BUFFER_STRONG: {
void *buffer = (void *)args[0];
size_t buffer_size = (size_t)args[1];
- args[0] = rng_fill_buffer_strong__verified(buffer, buffer_size);
+ rng_fill_buffer_strong__verified(buffer, buffer_size);
} break;
case SYSCALL_FIRMWARE_GET_VENDOR: {
### core/embed/sys/syscall/stm32/syscall_stubs.c
@@ -672,9 +672,9 @@ void rng_fill_buffer(void *buffer, size_t buffer_size) {
syscall_invoke2((uint32_t)buffer, buffer_size, SYSCALL_RNG_FILL_BUFFER);
}
-bool rng_fill_buffer_strong(void *buffer, size_t buffer_size) {
- return (bool)syscall_invoke2((uint32_t)buffer, buffer_size,
- SYSCALL_RNG_FILL_BUFFER_STRONG);
+void rng_fill_buffer_strong(void *buffer, size_t buffer_size) {
+ syscall_invoke2((uint32_t)buffer, buffer_size,
+ SYSCALL_RNG_FILL_BUFFER_STRONG);
}
// =============================================================================
### core/embed/sys/syscall/stm32/syscall_verifiers.c
@@ -825,16 +825,16 @@ void rng_fill_buffer__verified(void *buffer, size_t buffer_size) {
apptask_access_violation();
}
-bool rng_fill_buffer_strong__verified(void *buffer, size_t buffer_size) {
+void rng_fill_buffer_strong__verified(void *buffer, size_t buffer_size) {
if (!probe_write_access(buffer, buffer_size)) {
goto access_violation;
}
- return rng_fill_buffer_strong(buffer, buffer_size);
+ rng_fill_buffer_strong(buffer, buffer_size);
+ return;
access_violation:
apptask_access_violation();
- return false;
}
// ---------------------------------------------------------------------
### core/embed/sys/syscall/stm32/syscall_verifiers.h
@@ -221,7 +221,7 @@ secbool storage_next_counter__verified(const uint16_t key, uint32_t *count);
void rng_fill_buffer__verified(void *buffer, size_t buffer_size);
-bool rng_fill_buffer_strong__verified(void *buffer, size_t buffer_size);
+void rng_fill_buffer_strong__verified(void *buffer, size_t buffer_size);
// ---------------------------------------------------------------------
#include <io/translations.h>
### core/embed/upymod/modtrezorcrypto/modtrezorcrypto-random.h
@@ -55,11 +55,7 @@ static mp_obj_t mod_trezorcrypto_random_bytes(size_t n_args,
vstr_t vstr = {0};
vstr_init_len(&vstr, len);
if (n_args > 1 && mp_obj_is_true(args[1])) {
- if (!rng_fill_buffer_strong((uint8_t *)vstr.buf, len)) {
- vstr_clear(&vstr);
- mp_raise_msg(&mp_type_RuntimeError,
- MP_ERROR_TEXT("Failed to get strong randomness."));
- }
+ rng_fill_buffer_strong((uint8_t *)vstr.buf, len);
} else {
rng_fill_buffer((uint8_t *)vstr.buf, len);
}
### legacy/sec/rng_strong.h
@@ -25,9 +25,8 @@
// Minimal implementation of sec/rng_strong.h from core/embed
-static inline bool rng_fill_buffer_strong(void* buffer, size_t buffer_size) {
+static inline void rng_fill_buffer_strong(void* buffer, size_t buffer_size) {
random_buffer((uint8_t*)buffer, buffer_size);
- return true;
}
static inline void rng_fill_buffer_strong_time(uint32_t* time) {
### storage/storage.c
@@ -409,9 +409,7 @@ static secbool set_wipe_code(const uint8_t *wipe_code, size_t wipe_code_len) {
uint8_t *tag = salt + WIPE_CODE_SALT_SIZE;
memcpy(data, wipe_code, wipe_code_len);
- if (!rng_fill_buffer_strong(salt, WIPE_CODE_SALT_SIZE)) {
- return secfalse;
- }
+ rng_fill_buffer_strong(salt, WIPE_CODE_SALT_SIZE);
hmac_sha256(salt, WIPE_CODE_SALT_SIZE, wipe_code, wipe_code_len, tag);
secbool ret =
@@ -751,9 +749,7 @@ static secbool __wur derive_kek_set(const uint8_t *pin, size_t pin_len,
#endif
#endif
#if USE_TROPIC
- if (!rng_fill_buffer_strong(kek, SHA256_DIGEST_LENGTH)) {
- goto cleanup;
- }
+ rng_fill_buffer_strong(kek, SHA256_DIGEST_LENGTH);
if (tropic_pin_set_kek_masks(ui_progress, kek, stretched_pins) != true) {
goto cleanup;
}
@@ -869,9 +865,7 @@ static secbool set_pin(const uint8_t *pin, size_t pin_len,
uint8_t kek[SHA256_DIGEST_LENGTH] = {0};
uint8_t keiv[12] = {0};
chacha20poly1305_ctx ctx = {0};
- ensure(
- rng_fill_buffer_strong(rand_salt, STORAGE_SALT_SIZE) ? sectrue : secfalse,
- "rng_fill_buffer_strong failed");
+ rng_fill_buffer_strong(rand_salt, STORAGE_SALT_SIZE);
ensure(derive_kek_set(pin, pin_len, rand_salt, ext_salt, kek),
"derive_kek_set failed");
rfc7539_init(&ctx, kek, keiv);
@@ -938,9 +932,7 @@ static void init_wiped_storage(void) {
return;
}
- ensure(rng_fill_buffer_strong(cached_keys, sizeof(cached_keys)) ? sectrue
- : secfalse,
- "rng_fill_buffer_strong failed");
+ rng_fill_buffer_strong(cached_keys, sizeof(cached_keys));
unlocked = sectrue;
uint32_t version = NORCOW_VERSION;
ensure(auth_init(), "set_storage_auth_tag failed");
@@ -1970,9 +1962,7 @@ static secbool storage_upgrade(void) {
const uint16_t V0_PIN_FAIL_KEY = 0x0001;
secbool ret = secfalse;
if (norcow_active_version == 0) {
- if (!rng_fill_buffer_strong(cached_keys, sizeof(cached_keys))) {
- return secfalse;
- }
+ rng_fill_buffer_strong(cached_keys, sizeof(cached_keys));
// Initialize the storage authentication tag.
auth_init();
### storage/tests/c/sec/rng_strong.h
@@ -25,9 +25,8 @@
// Minimal implementation of sec/rng_strong.h for the storage tests
-static inline bool rng_fill_buffer_strong(void* buffer, size_t buffer_size) {
+static inline void rng_fill_buffer_strong(void* buffer, size_t buffer_size) {
random_buffer((uint8_t*)buffer, buffer_size);
- return true;
}
static inline void rng_fill_buffer_strong_time(uint32_t* time) {Why this scored 45/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.