fix(core/prodtest): Prevent deadlock between secrets-init and tropic-pair.
What changed, and why it matters
This change fixes a deadlock in Trezor's production-test firmware. The pairing process for a Tropic security chip now retrieves internal secrets before writing anything to flash memory. Previously, it could write the chip's public key to flash first, then call a routine that waits for another initialization step (secrets-init), causing both operations to get stuck waiting for each other. The fix simply reorders the steps so secrets are loaded first. This is a reliability bug in a factory tool, not a user-exploitable security vulnerability in the consumer wallet firmware.
Treat as a normal bug fix. Verify that production-test workflows using `tropic-pair` no longer hang when `secrets-init` and `tropic-pair` are invoked in either order. No special security response is indicated by the commit itself.
Security signals we found
Deadlock in production-test command
Flash write ordering dependency
Internal secrets initialization ordering
No input validation or buffer handling changes
No privilege escalation or code execution path introduced
Evidence from the diff
In prodtest_tropic_pair(), the code that retrieves the MCU-side Curve25519 pairing keys (secret_key_tropic_pairing_unprivileged() and secret_key_tropic_pairing_privileged()) is moved from after tropic_get_pubkey() and flash writes to before them. The commit message and comment state that secret_key_tropic_pairing_*() internally depends on secrets-init having completed, and that writing Tropic’s public key to MCU flash before that call could create a deadlock. The patch prevents the deadlock by ensuring the secrets dependency is satisfied before any flash write occurs. No cryptographic logic is changed; only the order of operations is altered.
Changed components
core/embed/projects/prodtest/cmd/prodtest_tropic.cTropic chip pairing command in production-test firmwareInspect captured patch +24 / −20
diff --git a/core/embed/projects/prodtest/.changelog.d/6337.fixed b/core/embed/projects/prodtest/.changelog.d/6337.fixed
new file mode 100644
index 000000000..26aaf409a
--- /dev/null
+++ b/core/embed/projects/prodtest/.changelog.d/6337.fixed
@@ -0,0 +1 @@
+Prevent deadlock between secrets-init and tropic-pair.
diff --git a/core/embed/projects/prodtest/cmd/prodtest_tropic.c b/core/embed/projects/prodtest/cmd/prodtest_tropic.c
index 52084b14a..0adba5725 100644
--- a/core/embed/projects/prodtest/cmd/prodtest_tropic.c
+++ b/core/embed/projects/prodtest/cmd/prodtest_tropic.c
@@ -746,6 +746,29 @@ static void prodtest_tropic_pair(cli_t* cli) {
lt_handle_t* tropic_handle = tropic_get_handle();
+ // Retrieve the unprivileged pairing key pair.
+ // NOTE: This ensures that secrets-init has already been called before any
+ // other steps take place. Otherwise, if we wrote Tropic's public key to the
+ // MCU's flash before completing secrets-init, we would run into a deadlock.
+ curve25519_key unprivileged_private = {0};
+ if (secret_key_tropic_pairing_unprivileged(unprivileged_private) != sectrue) {
+ cli_error(cli, CLI_ERROR,
+ "`secret_key_tropic_pairing_unprivileged()` failed.");
+ goto cleanup;
+ }
+ curve25519_key unprivileged_public = {0};
+ curve25519_scalarmult_basepoint(unprivileged_public, unprivileged_private);
+
+ // Retrieve the privileged pairing key pair.
+ curve25519_key privileged_private = {0};
+ if (secret_key_tropic_pairing_privileged(privileged_private) != sectrue) {
+ cli_error(cli, CLI_ERROR,
+ "`secret_key_tropic_pairing_privileged()` failed.");
+ goto cleanup;
+ }
+ curve25519_key privileged_public = {0};
+ curve25519_scalarmult_basepoint(privileged_public, privileged_private);
+
// Get the Tropic01 public pairing key from the chip's certificate.
curve25519_key tropic_public = {0};
if (!tropic_get_pubkey(tropic_public)) {
@@ -777,26 +800,6 @@ static void prodtest_tropic_pair(cli_t* cli) {
goto cleanup;
}
- // Retrieve the unprivileged pairing key pair.
- curve25519_key unprivileged_private = {0};
- if (secret_key_tropic_pairing_unprivileged(unprivileged_private) != sectrue) {
- cli_error(cli, CLI_ERROR,
- "`secret_key_tropic_pairing_unprivileged()` failed.");
- goto cleanup;
- }
- curve25519_key unprivileged_public = {0};
- curve25519_scalarmult_basepoint(unprivileged_public, unprivileged_private);
-
- // Retrieve the privileged pairing key pair.
- curve25519_key privileged_private = {0};
- if (secret_key_tropic_pairing_privileged(privileged_private) != sectrue) {
- cli_error(cli, CLI_ERROR,
- "`secret_key_tropic_pairing_privileged()` failed.");
- goto cleanup;
- }
- curve25519_key privileged_public = {0};
- curve25519_scalarmult_basepoint(privileged_public, privileged_private);
-
if (tropic_custom_session_start(TROPIC_FACTORY_PAIRING_KEY_SLOT) == LT_OK) {
// Write the privileged pairing key to the tropic's pairing key slot if it
// has not been written yet.
Why this scored 31/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.