chore(core): use fixed `TROPIC_SLOT_MAX_SIZE_V1`
What changed, and why it matters
This commit changes how Trezor firmware talks to a Tropic secure chip. It replaces a larger, possibly variable slot size (R_MEM_DATA_SIZE_MAX, 475 bytes) with a fixed smaller size (TROPIC_SLOT_MAX_SIZE_V1, 444 bytes) for backwards compatibility with older Tropic firmware. The change is described as a compatibility fix, not a security fix. It adds a compile-time size check to make sure a KEK mask buffer still fits in the smaller slot. There is no direct evidence in the commit of an exploitable vulnerability, but using a smaller fixed size reduces the risk of mismatched buffer sizes between the Trezor and Tropic chip.
Treat as a hardening/compatibility change rather than an active vulnerability. Review whether any production devices with Tropic firmware >=2.0.0 rely on the 475-byte slot size and confirm that capping at 444 bytes does not break functionality or truncate security-critical data. Continue normal regression testing of Tropic secure-element interactions.
Security signals we found
Buffer size constant changed from larger value to smaller fixed value
Added compile-time static assertion for buffer size
Syscall/secure-monitor verifiers updated to match new buffer size
Backwards-compatibility concern with external secure-element firmware versions
Evidence from the diff
The patch replaces all uses of R_MEM_DATA_SIZE_MAX with a new constant TROPIC_SLOT_MAX_SIZE_V1 (444 bytes) in Tropic-related code paths: prodtest commands, tropic.c secure-driver helpers, and syscall/smcall verifiers. The commit message states the 444-byte limit is for backwards compatibility with Tropic application firmware <2.0.0, while firmware >=2.0.0 supports 475 bytes. A _Static_assert is added to ensure the masks buffer in tropic_pin_set_kek_masks fits within the new 444-byte limit. The syscall/smcall verifiers now probe write access for 444 bytes instead of 475 bytes. No buffer overflow, underflow, or logic bug is visible in the diff; the change appears to be a defensive compatibility/consistency measure.
Changed components
core/embed/sec/tropic/tropic.ccore/embed/projects/prodtest/cmd/prodtest_tropic.ccore/embed/sys/syscall/stm32/syscall_verifiers.ccore/embed/sys/smcall/stm32/smcall_verifiers.ccore/embed/sec/tropic/inc/sec/tropic.hInspect captured patch +32 / −22
diff --git a/core/embed/projects/prodtest/cmd/prodtest_tropic.c b/core/embed/projects/prodtest/cmd/prodtest_tropic.c
index 7a3bf377b..77bcb75c6 100644
--- a/core/embed/projects/prodtest/cmd/prodtest_tropic.c
+++ b/core/embed/projects/prodtest/cmd/prodtest_tropic.c
@@ -1206,7 +1206,7 @@ static lt_ret_t data_write(lt_handle_t* h, uint16_t first_slot,
const size_t prefix_length = 2;
const size_t prefixed_data_length = data_length + prefix_length;
- const size_t total_slots_length = R_MEM_DATA_SIZE_MAX * slots_count;
+ const size_t total_slots_length = TROPIC_SLOT_MAX_SIZE_V1 * slots_count;
if (prefixed_data_length > total_slots_length) {
return LT_PARAM_ERR;
}
@@ -1233,12 +1233,12 @@ static lt_ret_t data_write(lt_handle_t* h, uint16_t first_slot,
}
ret = lt_r_mem_data_write(h, slot, prefixed_data + position,
- R_MEM_DATA_SIZE_MAX);
+ TROPIC_SLOT_MAX_SIZE_V1);
if (ret != LT_OK) {
return ret;
}
- position += R_MEM_DATA_SIZE_MAX;
+ position += TROPIC_SLOT_MAX_SIZE_V1;
slot += 1;
}
@@ -1257,7 +1257,7 @@ static lt_ret_t data_read(lt_handle_t* h, uint16_t first_slot,
// * It uses unnecessary amount of memory.
// * It reads from a data slot even if there is no data to be read.
- const size_t total_slots_length = R_MEM_DATA_SIZE_MAX * slots_count;
+ const size_t total_slots_length = TROPIC_SLOT_MAX_SIZE_V1 * slots_count;
uint8_t prefixed_data[total_slots_length];
size_t position = 0;
uint16_t slot = first_slot;
@@ -1265,16 +1265,16 @@ static lt_ret_t data_read(lt_handle_t* h, uint16_t first_slot,
while (slot <= last_data_slot) {
uint16_t slot_length = 0;
lt_ret_t ret = lt_r_mem_data_read(h, slot, prefixed_data + position,
- R_MEM_DATA_SIZE_MAX, &slot_length);
+ TROPIC_SLOT_MAX_SIZE_V1, &slot_length);
if (ret != LT_OK) {
return ret;
}
- if (slot_length != R_MEM_DATA_SIZE_MAX) {
+ if (slot_length != TROPIC_SLOT_MAX_SIZE_V1) {
return LT_FAIL;
}
- position += R_MEM_DATA_SIZE_MAX;
+ position += TROPIC_SLOT_MAX_SIZE_V1;
slot += 1;
}
@@ -1317,7 +1317,7 @@ static void cert_write(cli_t* cli, uint16_t first_slot, uint16_t slots_count) {
}
size_t certificate_length = 0;
- uint8_t certificate[R_MEM_DATA_SIZE_MAX * slots_count];
+ uint8_t certificate[TROPIC_SLOT_MAX_SIZE_V1 * slots_count];
if (!cli_arg_hex(cli, "hex-data", certificate, sizeof(certificate),
&certificate_length)) {
if (certificate_length == sizeof(certificate)) {
@@ -1356,7 +1356,7 @@ static void cert_write(cli_t* cli, uint16_t first_slot, uint16_t slots_count) {
}
size_t certificate_read_length = 0;
- uint8_t certificate_read[R_MEM_DATA_SIZE_MAX * slots_count];
+ uint8_t certificate_read[TROPIC_SLOT_MAX_SIZE_V1 * slots_count];
ret = data_read(tropic_handle, first_slot, slots_count, certificate_read,
sizeof(certificate_read), &certificate_read_length);
if (ret != LT_OK || certificate_read_length != certificate_length ||
@@ -1386,7 +1386,7 @@ static void cert_read(cli_t* cli, uint16_t first_slot, uint16_t slots_count) {
return;
}
- uint8_t certificate[R_MEM_DATA_SIZE_MAX * slots_count];
+ uint8_t certificate[TROPIC_SLOT_MAX_SIZE_V1 * slots_count];
size_t certificate_length = 0;
ret = data_read(tropic_get_handle(), first_slot, slots_count, certificate,
sizeof(certificate), &certificate_length);
diff --git a/core/embed/sec/tropic/inc/sec/tropic.h b/core/embed/sec/tropic/inc/sec/tropic.h
index 73278ace7..3be5cdd10 100644
--- a/core/embed/sec/tropic/inc/sec/tropic.h
+++ b/core/embed/sec/tropic/inc/sec/tropic.h
@@ -26,6 +26,10 @@
#include "ed25519-donna/ed25519.h"
+// Max size of data slot fixed to 444 B for backwards compatibility. From
+// Tropic's (RISCV) FW version >=2.0.0, 475 B can be utilized from each slot.
+#define TROPIC_SLOT_MAX_SIZE_V1 (444)
+
// FIDO attestation key and certificate.
#define TROPIC_FIDO_CERT_FIRST_SLOT 0
#define TROPIC_FIDO_CERT_SLOT_COUNT 3
diff --git a/core/embed/sec/tropic/tropic.c b/core/embed/sec/tropic/tropic.c
index 86835f59f..364af5df0 100644
--- a/core/embed/sec/tropic/tropic.c
+++ b/core/embed/sec/tropic/tropic.c
@@ -420,7 +420,7 @@ bool tropic_data_read(uint16_t udata_slot, uint8_t *data, uint16_t *size) {
}
lt_ret_t res = lt_r_mem_data_read(&drv->handle, udata_slot, data,
- R_MEM_DATA_SIZE_MAX, size);
+ TROPIC_SLOT_MAX_SIZE_V1, size);
return res == LT_OK;
}
@@ -809,6 +809,12 @@ bool tropic_pin_set_kek_masks(
uint16_t masked_kek_slot = get_kek_masks_slot(drv);
+ // Size of masks need to be smaller than or equal to TROPIC_SLOT_MAX_SIZE_V1
+ // for backwards compatibility. See the definition of TROPIC_SLOT_MAX_SIZE_V1
+ // for more details.
+ _Static_assert(TROPIC_SLOT_MAX_SIZE_V1 >= sizeof(masks),
+ "masks buffer too big");
+
if (TROPIC_RETRY_COMMAND(lt_r_mem_data_erase_write(
&drv->handle, masked_kek_slot, masks, sizeof(masks))) != LT_OK) {
goto cleanup;
@@ -840,16 +846,16 @@ bool tropic_pin_unmask_kek(
goto cleanup;
}
- uint8_t masks[R_MEM_DATA_SIZE_MAX] = {0};
+ uint8_t masks[TROPIC_SLOT_MAX_SIZE_V1] = {0};
_Static_assert(
- R_MEM_DATA_SIZE_MAX >= PIN_MAX_TRIES * TROPIC_MAC_AND_DESTROY_SIZE,
- "R_MEM_DATA_SIZE_MAX too small");
+ TROPIC_SLOT_MAX_SIZE_V1 >= PIN_MAX_TRIES * TROPIC_MAC_AND_DESTROY_SIZE,
+ "TROPIC_SLOT_MAX_SIZE_V1 too small");
uint16_t length = 0;
uint16_t masked_kek_slot = get_kek_masks_slot(drv);
if (TROPIC_RETRY_COMMAND(lt_r_mem_data_read(&drv->handle, masked_kek_slot,
- masks, R_MEM_DATA_SIZE_MAX,
+ masks, TROPIC_SLOT_MAX_SIZE_V1,
&length)) != LT_OK) {
goto cleanup;
}
@@ -883,7 +889,7 @@ bool tropic_data_multi_size(uint16_t first_slot, size_t *data_length) {
return false;
}
- uint8_t prefixed_data[R_MEM_DATA_SIZE_MAX];
+ uint8_t prefixed_data[TROPIC_SLOT_MAX_SIZE_V1];
uint16_t slot_length = 0;
if (!tropic_data_read(first_slot, prefixed_data, &slot_length)) {
return false;
@@ -909,7 +915,7 @@ bool tropic_data_multi_read(uint16_t first_slot, uint16_t slot_count,
}
uint16_t slot = first_slot;
- uint8_t slot_buffer[R_MEM_DATA_SIZE_MAX] = {0};
+ uint8_t slot_buffer[TROPIC_SLOT_MAX_SIZE_V1] = {0};
uint16_t slot_length = 0;
if (!tropic_data_read(slot, slot_buffer, &slot_length)) {
return false;
@@ -922,8 +928,8 @@ bool tropic_data_multi_read(uint16_t first_slot, uint16_t slot_count,
size_t out_length = slot_buffer[0] << 8 | slot_buffer[1];
uint16_t occupied_slot_count =
- (out_length + prefix_length + R_MEM_DATA_SIZE_MAX - 1) /
- R_MEM_DATA_SIZE_MAX;
+ (out_length + prefix_length + TROPIC_SLOT_MAX_SIZE_V1 - 1) /
+ TROPIC_SLOT_MAX_SIZE_V1;
if (out_length > max_data_length || occupied_slot_count > slot_count) {
return false;
}
@@ -941,7 +947,7 @@ bool tropic_data_multi_read(uint16_t first_slot, uint16_t slot_count,
uint16_t last_data_slot = first_slot + occupied_slot_count - 1;
while (slot < last_data_slot) {
// Non-terminal slots must be used to their full capacity.
- if (slot_length != R_MEM_DATA_SIZE_MAX) {
+ if (slot_length != TROPIC_SLOT_MAX_SIZE_V1) {
return false;
}
diff --git a/core/embed/sys/smcall/stm32/smcall_verifiers.c b/core/embed/sys/smcall/stm32/smcall_verifiers.c
index ffc21bba9..8037e8779 100644
--- a/core/embed/sys/smcall/stm32/smcall_verifiers.c
+++ b/core/embed/sys/smcall/stm32/smcall_verifiers.c
@@ -498,7 +498,7 @@ access_violation:
bool tropic_data_read__verified(uint16_t udata_slot, uint8_t *data,
uint16_t *size) {
- if (!probe_write_access(data, R_MEM_DATA_SIZE_MAX)) {
+ if (!probe_write_access(data, TROPIC_SLOT_MAX_SIZE_V1)) {
goto access_violation;
}
diff --git a/core/embed/sys/syscall/stm32/syscall_verifiers.c b/core/embed/sys/syscall/stm32/syscall_verifiers.c
index d99971d4d..969ee76d1 100644
--- a/core/embed/sys/syscall/stm32/syscall_verifiers.c
+++ b/core/embed/sys/syscall/stm32/syscall_verifiers.c
@@ -1296,7 +1296,7 @@ access_violation:
bool tropic_data_read__verified(uint16_t udata_slot, uint8_t *data,
uint16_t *size) {
- if (!probe_write_access(data, R_MEM_DATA_SIZE_MAX)) {
+ if (!probe_write_access(data, TROPIC_SLOT_MAX_SIZE_V1)) {
goto access_violation;
}
Why this scored 25/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.