feat(core): app root packet downgrade protection
What changed, and why it matters
This commit adds downgrade protection for a new 'app root packet' system in Trezor firmware. Previously, the code had a TODO note saying downgrade protection needed to be considered. The change makes the device remember the timestamps of previously accepted root packets and reject older or inconsistent ones. It also adds a new Python-exposed state object so the wallet software can persist these timestamps. This is a security-hardening feature rather than a fix for a currently exploitable bug, but it closes a design gap that could have allowed an attacker to roll back trusted application lists to older, potentially vulnerable versions.
Review the storage lifecycle of AppRootState to ensure it cannot be reset or erased by untrusted code, which would re-enable downgrade. Verify that the 90-day ROOT_PACKET_MAX_DRIFT and the contiguous ring_mask policy match the intended trust model. Confirm that all callers of app_root_update() (including tests and emulators) have been updated to pass and persist the new state object. Consider whether an explicit monotonic counter or secure storage binding is needed instead of relying solely on caller-persisted timestamps.
Security signals we found
Replaces a TODO comment ('!@# TODO: Consider downgrade protection') with concrete timestamp-based anti-downgrade checks
Adds per-ring timestamp state to prevent rollback of root-of-trust packets
Adds chain_timestamp field and 90-day drift bound to root packet format
Adds contiguous ring_mask validation to prevent non-contiguous ring selections
Extends syscall ABI and adds write-access verification for the new state argument
Adds MicroPython wrapper for persisting root packet state
Evidence from the diff
The patch implements anti-downgrade checks in app_root_update() and root_packet_verify(). app_root_update() now receives an app_root_state_t containing per-ring timestamps; it rejects any root packet whose timestamp is older than the stored timestamp for a ring being updated, and ensures lower-priority rings are not older than the highest-priority updated ring. root_packet_verify() now requires ring_mask to be contiguous, enforces chain_timestamp == 0 for ring 0, and limits timestamp drift between a child and its parent root packet to 90 days. A new MicroPython AppRootState class is added to encode/decode and persist these timestamps across root_update() calls. The syscall ABI for SYSCALL_APP_ROOT_UPDATE is extended from two to three arguments, and the verifier checks write access to the state buffer.
Changed components
core/embed/io/app_arena/app_root.ccore/embed/io/app_arena/inc/io/app_root.hcore/embed/io/app_arena/root_packet.ccore/embed/io/app_arena/root_packet.hcore/embed/sys/syscall/stm32/syscall_dispatch.ccore/embed/sys/syscall/stm32/syscall_stubs.ccore/embed/sys/syscall/stm32/syscall_verifiers.ccore/embed/sys/syscall/stm32/syscall_verifiers.hcore/embed/upymod/modtrezorapp/modtrezorapp-root.hcore/embed/upymod/modtrezorapp/modtrezorapp.ccore/embed/upymod/modtrezorutils/modtrezorutils.ccore/mocks/generated/trezorapp.pyiInspect captured patch +335 / −31
### core/embed/io/app_arena/app_root.c
@@ -63,8 +63,19 @@ ts_t app_root_init(void) {
TSH_RETURN;
}
-ts_t app_root_update(const void* root_packet_data,
- size_t root_packet_data_size) {
+// Returns the index of the first set bit in the ring mask,
+// or -1 if no bits are set.
+static int first_updated_ring(uint8_t ring_mask) {
+ for (int id = 0; id < APP_RING_COUNT; id++) {
+ if (ring_mask & (1 << id)) {
+ return id;
+ }
+ }
+ return -1;
+}
+
+ts_t app_root_update(const void* root_packet_data, size_t root_packet_data_size,
+ app_root_state_t* state) {
TSH_DECLARE;
ts_t status;
@@ -78,11 +89,27 @@ ts_t app_root_update(const void* root_packet_data,
TSH_CHECK(root_packet != NULL, TS_EBADMSG);
- // !@# TODO: Consider downgrade protection
+ // Check that any updated ring is not downgraded
+ for (int id = 0; id < APP_RING_COUNT; id++) {
+ if (root_packet->ring_mask & (1 << id)) {
+ TSH_CHECK(root_packet->timestamp >= state->ring_timestamp[id],
+ TS_EBADMSG);
+ }
+ }
+
+ // Ensure that lower-priority rings (with higher id) are not older than
+ // higher-priority rings
+ int first_id = first_updated_ring(root_packet->ring_mask);
+ if (first_id > 0) {
+ TSH_CHECK(
+ root_packet->chain_timestamp >= state->ring_timestamp[first_id - 1],
+ TS_EBADMSG);
+ }
int slot = 0;
for (int id = 0; id < APP_RING_COUNT; id++) {
if (root_packet->ring_mask & (1 << id)) {
+ state->ring_timestamp[id] = root_packet->timestamp;
root->ring[id].timestamp = root_packet->timestamp;
root->ring[id].merkle_root = root_packet->merkle_root[slot];
++slot;
### core/embed/io/app_arena/inc/io/app_root.h
@@ -31,6 +31,13 @@ typedef enum {
APP_RING_COUNT,
} app_ring_t;
+/**
+ * @brief Hold persisted timestamps for each application ring
+ */
+typedef struct {
+ uint32_t ring_timestamp[APP_RING_COUNT];
+} app_root_state_t;
+
/**
* @brief Initializes the root-of-trust storage.
*
@@ -44,12 +51,19 @@ ts_t app_root_init(void);
* Before storing, the function checks the integrity and validity of the
* root packet, including its signature.
*
+ * Root packets timestamps are compared against the provided state structure
+ * to ensure that the new root packet is more recent than the stored timestamps.
+ * state structure is updated with new values and caller is responsible
+ * for storing it for future reference.
+ *
* @param root_packet Pointer to the root packet to store.
* @param root_packet_size Size of the root packet in bytes.
+ * @param state Pointer to an app_root_state_t structure
*
* @return TS_OK on success, or an error code on failure.
*/
-ts_t app_root_update(const void* root_packet, size_t root_packet_size);
+ts_t app_root_update(const void* root_packet, size_t root_packet_size,
+ app_root_state_t* state);
/**
* @brief Deletes all stored root packets
### core/embed/io/app_arena/root_packet.c
@@ -28,6 +28,12 @@
#include "root_packet.h"
+#include <stdlib.h>
+
+// Maximum allowed time difference between the root packet and its
+// higher-level root packet.
+#define ROOT_PACKET_MAX_DRIFT (90 * 86400) // 90 days
+
static const mldsa44_public_key_t * const ROOT_PACKET_KEYS[] = {
#if defined(BOOTLOADER_DEVEL) || defined(TREZOR_EMULATOR)
(const mldsa44_public_key_t*)
@@ -201,6 +207,7 @@ static const mldsa44_public_key_t * const ROOT_PACKET_KEYS[] = {
#endif
};
+// Returns the number of set bits in the given value.
static int popcount(uint8_t value) {
int count = 0;
while (value != 0) {
@@ -212,6 +219,16 @@ static int popcount(uint8_t value) {
return count;
}
+// Returns true if the set bits in mask form a single contiguous run.
+// Assumes mask != 0
+static bool is_contiguous_mask(uint8_t mask) {
+ // mask | (mask - 1) sets all bits below the lowest set bit.
+ // For a contiguous run the result is 2^n - 1, so adding one yields
+ // a single power of two, i.e. x & (x - 1) == 0.
+ uint32_t x = (uint32_t)(mask | (mask - 1)) + 1;
+ return (x & (x - 1)) == 0;
+}
+
ts_t root_packet_verify(const void* data, size_t size,
root_packet_auth_t** out) {
TSH_DECLARE;
@@ -233,6 +250,16 @@ ts_t root_packet_verify(const void* data, size_t size,
TSH_CHECK(auth->ring_mask != 0, TS_EBADMSG);
TSH_CHECK(auth->ring_mask <= (1 << APP_RING_COUNT) - 1, TS_EBADMSG);
TSH_CHECK(auth->timestamp != 0, TS_EBADMSG);
+ TSH_CHECK(is_contiguous_mask(auth->ring_mask), TS_EBADMSG);
+
+ if (auth->ring_mask & (1 << APP_RING_0)) {
+ // Ring #0 - no chain timestamp
+ TSH_CHECK(auth->chain_timestamp == 0, TS_EBADMSG);
+ } else {
+ // Ring #1 and/or #2
+ int32_t diff = (int32_t)(auth->timestamp - auth->chain_timestamp);
+ TSH_CHECK(abs(diff) <= ROOT_PACKET_MAX_DRIFT, TS_EBADMSG);
+ }
// Calculate the expected size of the authenticated part of the root packet
size_t auth_part_size = sizeof(root_packet_auth_t) +
@@ -258,7 +285,8 @@ ts_t root_packet_verify(const void* data, size_t size,
TSH_CHECK(popcount(sigmask) == ARRAY_LENGTH(unauth->signature), TS_EBADMSG);
- for (size_t sig_idx = 0; sig_idx < ARRAY_LENGTH(unauth->signature); sig_idx++) {
+ for (size_t sig_idx = 0; sig_idx < ARRAY_LENGTH(unauth->signature);
+ sig_idx++) {
// Get the index of the public key in the signature mask
size_t key_idx = __builtin_ctz(sigmask);
TSH_CHECK(key_idx < ARRAY_LENGTH(ROOT_PACKET_KEYS), TS_EBADMSG);
### core/embed/io/app_arena/root_packet.h
@@ -43,6 +43,8 @@ typedef struct {
uint8_t reserved[2];
/** Root packet timestamp */
uint32_t timestamp;
+ /** Timestamp of the higher-level root packet or 0 */
+ uint32_t chain_timestamp;
/** Merkle roots for the rings in ring_mask. The roots are stored
* in the order of the bits in ring_mask, from least significant to
* most significant. */
### core/embed/sys/syscall/stm32/syscall_dispatch.c
@@ -1051,7 +1051,9 @@ __attribute((no_stack_protector)) void syscall_handler(uint32_t *args,
case SYSCALL_APP_ROOT_UPDATE: {
const void *root_packet = (const void *)args[0];
size_t root_packet_size = (size_t)args[1];
- ts_t status = app_root_update__verified(root_packet, root_packet_size);
+ app_root_state_t *state = (app_root_state_t *)args[2];
+ ts_t status =
+ app_root_update__verified(root_packet, root_packet_size, state);
args[0] = ts_code(status);
} break;
### core/embed/sys/syscall/stm32/syscall_stubs.c
@@ -1005,9 +1005,10 @@ bool tropic_data_read(uint16_t udata_slot, uint8_t *data, uint16_t *size) {
#include <io/app_root.h>
-ts_t app_root_update(const void *root_packet, size_t root_packet_size) {
- return ts_make(syscall_invoke2((uint32_t)root_packet, root_packet_size,
- SYSCALL_APP_ROOT_UPDATE));
+ts_t app_root_update(const void *root_packet, size_t root_packet_size,
+ app_root_state_t *state) {
+ return ts_make(syscall_invoke3((uint32_t)root_packet, root_packet_size,
+ (uint32_t)state, SYSCALL_APP_ROOT_UPDATE));
}
bool app_root_is_loaded(app_ring_t ring) {
### core/embed/sys/syscall/stm32/syscall_verifiers.c
@@ -1548,13 +1548,17 @@ bool tropic_data_read__verified(uint16_t udata_slot, uint8_t *data,
#ifdef USE_APP_LOADING
-ts_t app_root_update__verified(const void *root_packet,
- size_t root_packet_size) {
+ts_t app_root_update__verified(const void *root_packet, size_t root_packet_size,
+ app_root_state_t *state) {
if (!probe_read_access(root_packet, root_packet_size)) {
goto access_violation;
}
- return app_root_update(root_packet, root_packet_size);
+ if (!probe_write_access(state, sizeof(*state))) {
+ goto access_violation;
+ }
+
+ return app_root_update(root_packet, root_packet_size, state);
access_violation:
apptask_access_violation();
### core/embed/sys/syscall/stm32/syscall_verifiers.h
@@ -378,10 +378,10 @@ bool tropic_data_read__verified(uint16_t udata_slot, uint8_t *data,
#include <io/app_root.h>
-ts_t app_root_update__verified(const void *root_packet,
- size_t root_packet_size);
+ts_t app_root_update__verified(const void *root_packet, size_t root_packet_size,
+ app_root_state_t *state);
-ts_t app_root_get_timestamp__verified(app_ring_t ring, uint32_t *timestamp);
+ts_t app_root_get_timestamp__verified(app_ring_t ring, uint32_t *state);
#include <io/app_arena.h>
### core/embed/upymod/modtrezorapp/modtrezorapp-root.h
@@ -0,0 +1,188 @@
+/*
+ * 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 <py/obj.h>
+#include <py/runtime.h>
+
+#include <trezor_rtl.h>
+
+#include <io/app_arena.h>
+
+/// class AppRootState:
+/// """
+/// Represents the persisted state of root packets, including the minimum
+/// timestamps for the three rings. The structure is opaque to MicroPython
+/// and can be accessed only through its encoded representation, which can
+/// be retrieved from or stored in persistent storage.
+/// """
+typedef struct {
+ mp_obj_base_t base;
+ app_root_state_t state;
+} mp_obj_AppRootState_t;
+
+// Version 1 of the AppRootState structure
+typedef struct {
+ // timestamp for the three rings
+ uint32_t timestamp[3];
+} app_root_state_v1_t;
+
+// Encoded representation of the AppRootState structure
+typedef struct {
+ // version of the encoded structure (v1 => 1)
+ uint32_t version;
+ union {
+ app_root_state_v1_t v1;
+ } data;
+} app_root_state_encoded_t;
+
+// Encoded app_root_state_t size for v1
+#define APP_ROOT_STATE_ENCODED_V1_SIZE \
+ (offsetof(app_root_state_encoded_t, data) + sizeof(app_root_state_v1_t))
+
+// Decodes the encoded representation of the AppRootState structure
+static ts_t app_root_state_decode(app_root_state_t *state, const uint8_t *data,
+ size_t data_size) {
+ TSH_DECLARE;
+
+ TSH_CHECK_ARG(state != NULL);
+ TSH_CHECK_ARG(data != NULL);
+ TSH_CHECK_ARG(data_size >= offsetof(app_root_state_encoded_t, data));
+ TSH_CHECK_ARG(data_size <= sizeof(app_root_state_encoded_t));
+
+ app_root_state_encoded_t encoded = {0};
+ memcpy(&encoded, data, data_size);
+
+ switch (encoded.version) {
+ case 1:
+ TSH_CHECK(data_size == APP_ROOT_STATE_ENCODED_V1_SIZE, TS_EBADMSG);
+
+ state->ring_timestamp[0] = encoded.data.v1.timestamp[0];
+ state->ring_timestamp[1] = encoded.data.v1.timestamp[1];
+ state->ring_timestamp[2] = encoded.data.v1.timestamp[2];
+
+ break;
+ default:
+ TSH_RAISE(TS_EBADMSG);
+ }
+
+cleanup:
+ TSH_RETURN;
+}
+
+// Encodes the app_root_state_t structure into its encoded representation
+static ts_t app_root_state_encode(const app_root_state_t *state, void *buffer,
+ size_t buffer_size, size_t *encoded_size) {
+ TSH_DECLARE;
+
+ TSH_CHECK_ARG(state != NULL);
+ TSH_CHECK_ARG(buffer != NULL);
+ TSH_CHECK_ARG(encoded_size != NULL);
+ TSH_CHECK_ARG(buffer_size >= sizeof(app_root_state_encoded_t));
+
+ app_root_state_encoded_t encoded = {
+ .version = 1,
+ .data.v1.timestamp =
+ {
+ [0] = state->ring_timestamp[0],
+ [1] = state->ring_timestamp[1],
+ [2] = state->ring_timestamp[2],
+ },
+ };
+
+ size_t size = APP_ROOT_STATE_ENCODED_V1_SIZE;
+ memcpy(buffer, &encoded, size);
+ *encoded_size = size;
+
+cleanup:
+ TSH_RETURN;
+}
+
+/// def __init__(self, min_timestamp: uint | None, state: bytes | None = None)
+/// -> None:
+/// """
+/// Creates an AppRootState object.
+/// """
+static mp_obj_t mod_trezorapp_AppRootState_make_new(const mp_obj_type_t *type,
+ size_t n_args, size_t n_kw,
+ const mp_obj_t *args) {
+ mp_arg_check_num(n_args, n_kw, 0, 2, false);
+ mp_obj_AppRootState_t *o = mp_obj_malloc(mp_obj_AppRootState_t, type);
+
+ memset(&o->state, 0, sizeof(app_root_state_t));
+
+ // If a state is provided, deserialize it
+ if (n_args >= 2 && args[1] != mp_const_none) {
+ // deserialize the state from the provided bytes
+ mp_buffer_info_t bufinfo;
+ mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_READ);
+
+ ts_t status = app_root_state_decode(&o->state, bufinfo.buf, bufinfo.len);
+ if (ts_error(status)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("Failed to decode AppRootState"));
+ }
+ }
+
+ // Apply the minimum timestamp to the ring timestamps
+ if (n_args >= 1 && args[0] != mp_const_none) {
+ mp_uint_t min_timestamp = mp_obj_get_uint(args[0]);
+ for (size_t i = 0; i < APP_RING_COUNT; i++) {
+ o->state.ring_timestamp[i] =
+ MAX(min_timestamp, o->state.ring_timestamp[i]);
+ }
+ }
+
+ return MP_OBJ_FROM_PTR(o);
+}
+
+/// def serialize(self) -> bytes:
+/// """
+/// Serializes the AppRootState object to bytes.
+/// """
+static mp_obj_t mod_trezorapp_AppRootState_serialize(mp_obj_t self) {
+ mp_obj_AppRootState_t *o = MP_OBJ_TO_PTR(self);
+
+ app_root_state_encoded_t encoded_state;
+ size_t encoded_size = 0;
+
+ ts_t status =
+ app_root_state_encode(&o->state, &encoded_state,
+ sizeof(app_root_state_encoded_t), &encoded_size);
+ if (ts_error(status)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("Failed to encode AppRootState"));
+ }
+
+ return mp_obj_new_bytes((const byte *)&encoded_state, encoded_size);
+}
+static MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorapp_AppRootState_serialize_obj,
+ mod_trezorapp_AppRootState_serialize);
+
+static const mp_rom_map_elem_t mod_trezorapp_AppRootState_locals_dict_table[] =
+ {
+ {MP_ROM_QSTR(MP_QSTR_serialize),
+ MP_ROM_PTR(&mod_trezorapp_AppRootState_serialize_obj)},
+};
+static MP_DEFINE_CONST_DICT(mod_trezorapp_AppRootState_locals_dict,
+ mod_trezorapp_AppRootState_locals_dict_table);
+
+// clang-format off
+static MP_DEFINE_CONST_OBJ_TYPE(mod_trezorapp_AppRootState_type,
+ MP_QSTR_AppRootState, MP_TYPE_FLAG_NONE,
+ make_new, mod_trezorapp_AppRootState_make_new,
+ locals_dict, &mod_trezorapp_AppRootState_locals_dict);
+// clang-format on
### core/embed/upymod/modtrezorapp/modtrezorapp.c
@@ -32,6 +32,7 @@
#include "../trezorobj.h"
#include "modtrezorapp-image.h"
+#include "modtrezorapp-root.h"
/// package: trezorapp
@@ -49,7 +50,7 @@ static mp_obj_t mod_trezorapp_create_image(mp_obj_t header_obj,
mp_buffer_info_t proof_buf;
mp_get_buffer_raise(proof_obj, &proof_buf, MP_BUFFER_READ);
- mp_obj_AppImage_t *o =
+ mp_obj_AppImage_t* o =
mp_obj_malloc(mp_obj_AppImage_t, &mod_trezorapp_AppImage_type);
ts_t status = app_arena_create_image(
@@ -74,7 +75,7 @@ typedef struct {
} mp_obj_AppImageIter_t;
static mp_obj_t mod_trezorapp_images_iternext(mp_obj_t self_in) {
- mp_obj_AppImageIter_t *self = MP_OBJ_TO_PTR(self_in);
+ mp_obj_AppImageIter_t* self = MP_OBJ_TO_PTR(self_in);
app_image_handle_t handle = APP_IMAGE_HANDLE_INVALID;
ts_t status = app_arena_next_image(&self->state, &handle);
@@ -86,7 +87,7 @@ static mp_obj_t mod_trezorapp_images_iternext(mp_obj_t self_in) {
return MP_OBJ_STOP_ITERATION;
}
- mp_obj_AppImage_t *o =
+ mp_obj_AppImage_t* o =
mp_obj_malloc(mp_obj_AppImage_t, &mod_trezorapp_AppImage_type);
o->handle = handle;
return MP_OBJ_FROM_PTR(o);
@@ -103,7 +104,7 @@ static MP_DEFINE_CONST_OBJ_TYPE(mod_trezorapp_AppImageIter_type,
/// Return an iterator over all app images in the app arena.
/// """
static mp_obj_t mod_trezorapp_images(void) {
- mp_obj_AppImageIter_t *o =
+ mp_obj_AppImageIter_t* o =
mp_obj_malloc(mp_obj_AppImageIter_t, &mod_trezorapp_AppImageIter_type);
o->state = APP_IMAGE_ITER_INIT;
return MP_OBJ_FROM_PTR(o);
@@ -126,7 +127,7 @@ static mp_obj_t mod_trezorapp_arena_image_by_handle(mp_obj_t handle_obj) {
mp_raise_type(&mp_type_AppImageError);
}
- mp_obj_AppImage_t *o =
+ mp_obj_AppImage_t* o =
mp_obj_malloc(mp_obj_AppImage_t, &mod_trezorapp_AppImage_type);
o->handle = handle;
return MP_OBJ_FROM_PTR(o);
@@ -197,24 +198,35 @@ static mp_obj_t mod_trezorapp_arena_mem_free(void) {
static MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorapp_arena_mem_free_obj,
mod_trezorapp_arena_mem_free);
-/// def root_update(root_packet: AnyBytes) -> None:
+/// def root_update(root_packet: AnyBytes, state: AppRootState) -> None:
/// """
/// Update the root-of-trust storage with the provided root packet.
-/// The root packet is verified for integrity and validity before being
-/// stored. If the verification fails, an AppArenaError is raised.
+/// The root packet is verified for integrity and validity, and its
+/// timestamps are checked against the minimum timestamps in `state`
+/// before being stored. If the verification fails, an AppArenaError is
+/// raised. If the root packet timestamps are newer, `state` is updated
+/// accordingly.
/// """
-static mp_obj_t mod_trezorapp_root_update(mp_obj_t root_packet_obj) {
+static mp_obj_t mod_trezorapp_root_update(mp_obj_t root_packet_obj,
+ mp_obj_t root_state_obj) {
mp_buffer_info_t root_packet_buf;
mp_get_buffer_raise(root_packet_obj, &root_packet_buf, MP_BUFFER_READ);
- ts_t status = app_root_update(root_packet_buf.buf, root_packet_buf.len);
+ if (!mp_obj_is_type(root_state_obj, &mod_trezorapp_AppRootState_type)) {
+ mp_raise_TypeError(MP_ERROR_TEXT("AppRootState required"));
+ }
+
+ mp_obj_AppRootState_t* o = MP_OBJ_TO_PTR(root_state_obj);
+
+ ts_t status =
+ app_root_update(root_packet_buf.buf, root_packet_buf.len, &o->state);
if (ts_error(status)) {
mp_raise_type(&mp_type_AppArenaError);
}
return mp_const_none;
}
-static MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorapp_root_update_obj,
+static MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorapp_root_update_obj,
mod_trezorapp_root_update);
/// def root_is_loaded(ring: uint) -> bool:
@@ -273,6 +285,8 @@ static MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorapp_app_ring_from_header_obj,
static const mp_rom_map_elem_t mod_module_trezorapp_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_trezorapp)},
{MP_ROM_QSTR(MP_QSTR_AppImage), MP_ROM_PTR(&mod_trezorapp_AppImage_type)},
+ {MP_ROM_QSTR(MP_QSTR_AppRootState),
+ MP_ROM_PTR(&mod_trezorapp_AppRootState_type)},
{MP_ROM_QSTR(MP_QSTR_AppError), MP_ROM_PTR(&mp_type_AppError)},
{MP_ROM_QSTR(MP_QSTR_AppImageError), MP_ROM_PTR(&mp_type_AppImageError)},
{MP_ROM_QSTR(MP_QSTR_AppImageNotFoundError),
@@ -309,7 +323,7 @@ static MP_DEFINE_CONST_DICT(mp_module_trezorapp_globals,
const mp_obj_module_t mp_module_trezorapp = {
.base = {&mp_type_module},
- .globals = (mp_obj_dict_t *)&mp_module_trezorapp_globals,
+ .globals = (mp_obj_dict_t*)&mp_module_trezorapp_globals,
};
MP_REGISTER_MODULE(MP_QSTR_trezorapp, mp_module_trezorapp);
### core/embed/upymod/modtrezorutils/modtrezorutils.c
@@ -796,7 +796,7 @@ static MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorutil_get_scm_revision_obj,
/// The JSON file can be decoded by analyze-memory-dump.py
/// """
static mp_obj_t mod_trezorutils_meminfo(mp_obj_t filename) {
- FILE *out = NULL;
+ FILE* out = NULL;
if (filename != mp_const_none) {
size_t fn_len = 0;
out = fopen(mp_obj_str_get_data(filename, &fn_len), "w");
### core/mocks/generated/trezorapp.pyi
@@ -150,6 +150,27 @@ class AppImage:
"""
+# upymod/modtrezorapp/modtrezorapp-root.h
+class AppRootState:
+ """
+ Represents the persisted state of root packets, including the minimum
+ timestamps for the three rings. The structure is opaque to MicroPython
+ and can be accessed only through its encoded representation, which can
+ be retrieved from or stored in persistent storage.
+ """
+
+ def __init__(self, min_timestamp: int | None, state: bytes | None = None)
+ -> None:
+ """
+ Creates an AppRootState object.
+ """
+
+ def serialize(self) -> bytes:
+ """
+ Serializes the AppRootState object to bytes.
+ """
+
+
# upymod/modtrezorapp/modtrezorapp.c
def create_image(header: AnyBytes, proof: AnyBytes) -> AppImage:
"""
@@ -203,11 +224,14 @@ def mem_free() -> int:
# upymod/modtrezorapp/modtrezorapp.c
-def root_update(root_packet: AnyBytes) -> None:
+def root_update(root_packet: AnyBytes, state: AppRootState) -> None:
"""
Update the root-of-trust storage with the provided root packet.
- The root packet is verified for integrity and validity before being
- stored. If the verification fails, an AppArenaError is raised.
+ The root packet is verified for integrity and validity, and its
+ timestamps are checked against the minimum timestamps in `state`
+ before being stored. If the verification fails, an AppArenaError is
+ raised. If the root packet timestamps are newer, `state` is updated
+ accordingly.
"""
Why this scored 59/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.