Skip to content

SFT-7320: Improve Passport Core hardware error handling - #655

Open
FoundationKen wants to merge 1 commit into
dev-v2.4.0from
sft-7320-entropy-hardening
Open

SFT-7320: Improve Passport Core hardware error handling#655
FoundationKen wants to merge 1 commit into
dev-v2.4.0from
sft-7320-entropy-hardening

Conversation

@FoundationKen

Copy link
Copy Markdown
Contributor

Propagate entropy failures, add bounded MCU RNG health checks, and fail closed with a visible fatal error.

These are strictly defense-in-depth and UX improvements in case an entropy source fails.

There was no case where a low-entropy seed was created.

Propagate entropy failures, add bounded MCU RNG health checks, and fail closed with a visible fatal error.
// Check if data register contains valid random data
while (!(RNG->SR & RNG_SR_DRDY)) {
// busy wait; okay to get stuck here... better than failing.
while ((DWT->CYCCNT - start_cycle) < timeout_cycles) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: I'd just use a fixed cycle count for simplicity. The internals of the new rng_cycle_counter_setup function is very arcane, and the registers might not even be available in production units.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or use HAL_GetTick()

/// """
/// Read random bytes from multiple noise sources.
/// """
STATIC mp_obj_t mod_passport_Noise_random_bytes(mp_obj_t self,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This can now only return true btw, making the if not check above dead code.

The return value might as well be None.

// Enable the RNG
// Start each image from a known peripheral state. Clearing the latched
// interrupt flags and restarting the generator is the recovery sequence
// recommended by ST after a seed error. A persistent current error is

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documented sequence is clear-SEIS then discard 12 words. Either do the discard part or drop the claim.

uint32_t rng_sample(void) {
bool rng_try_sample(uint32_t* result) {
static uint32_t last_rng_result;
static bool have_last_rng_result;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is needed, just initialize last_rng_result to 0. The first value shouldn't be 0 anyway.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the diff drops the previous ==0 checks that Linux also does.

// sample is a hard failure; callers must not silently degrade.
uint32_t status = RNG->SR;
if (status & error_mask) {
return false;

@badicsalex badicsalex Aug 1, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: The error bits are latching, and it's been reported to happen spuriously. There is a documented recovery procedure from it, and it might be worth implementing for stability (on SEIS/SECS, clear SEIS, discard 12 words from RNG_DR, re-check; retry up to 3x before failing)

Also Linux and ST code considers CECS/CEIS to be non-fatal, they just clear it and continue.

Comment thread ports/stm32/rng.c
#define RNG_TIMEOUT_MS (10)

uint32_t rng_get(void) {
#if defined(MICROPY_PASSPORT)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: this plumbing has port code including a board header (pprng.h), which is backwards from everything else here. boardctrl.h already has the convention for this, and boards/Passport/mpconfigboard.h already uses two of those hooks. Same shape works:

/* mpconfigboard.h */
#define MICROPY_BOARD_RNG_GET rng_sample
uint32_t rng_sample(void);

/* rng.c, instead of this #if */
#ifdef MICROPY_BOARD_RNG_GET
return MICROPY_BOARD_RNG_GET();
#else
same as now
#endif

Same behaviour.

extern void __attribute__((noreturn)) __fatal_error(const char* msg);

void rng_fatal_error(void) {
__fatal_error("Entropy source failure");

@badicsalex badicsalex Aug 1, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This just freezes the screen on WFI and doesn't actually display anything. I'm not sure that's friendly.

A straight reboot is probably better. (trying to display something would be a much larger change)

uint32_t us_to_delay = rng_sample() % 10000;
delay_us(us_to_delay);
uint32_t us_to_delay = 0;
(void)rng_try_sample(&us_to_delay);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This change, and it's friend in bootloader/main.c is not really needed IMO. If the rng has an issue, the device will die microseconds after this by running the actual rng_sample. In fact, this lets an attacker glitch the clock and disable the boot stepping mitigation for free.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants