fix(core): don't fail `try_get_ctx_ids()` when invoked via UI
What changed, and why it matters
This commit fixes a bug in the Trezor hardware wallet firmware where a function called try_get_ctx_ids() would crash when triggered through the device's user interface rather than over the wire protocol. The crash blocked device wipe from completing on the T3W1 model if a recovery process was aborted. The fix catches the missing-wire-context case and returns no IDs instead of raising an exception, allowing the wipe to proceed. There is no direct evidence in the commit that this is exploitable by an attacker; it appears to be a reliability/functional bug fix.
Treat as a functional bug fix with low security risk. Review whether any other UI-invoked callers of get_context() or try_get_ctx_ids() need similar hardening, and verify that returning None from try_get_ctx_ids() does not bypass intended authorization checks in THP session handling.
Security signals we found
Unhandled exception in context-lookup helper function
UI-invoked code path lacked wire context, causing flow abort
Device wipe blocked on T3W1 after aborted recovery
Fix catches NoWireContext and returns None safely
Evidence from the diff
In core/src/trezor/wire/context.py, try_get_ctx_ids() now wraps get_context() in a try/except for NoWireContext and returns None when there is no wire context. Previously, calling this function from a UI flow (where no wire context exists) raised NoWireContext, which aborted the flow. The commit message states this prevented device wipe on T3W1 after recovery was aborted. The test change creates a new THP (Trezor Host Protocol) client after wipe because the prior channel state is invalidated. The fixtures.json update reflects a changed UI hash for the affected test.
Changed components
core/src/trezor/wire/context.pytests/persistence_tests/test_shamir_persistence.pytests/ui_tests/fixtures.jsonTrezor Core firmware (T3W1 model specifically mentioned)Inspect captured patch +8 / −2
diff --git a/core/src/trezor/wire/context.py b/core/src/trezor/wire/context.py
index 3560b17e7..28fdfeb00 100644
--- a/core/src/trezor/wire/context.py
+++ b/core/src/trezor/wire/context.py
@@ -151,7 +151,10 @@ def try_get_ctx_ids() -> tuple[bytes, bytes] | None:
if utils.USE_THP:
from trezor.wire.thp.session_context import GenericSessionContext
- ctx = get_context()
+ try:
+ ctx = get_context()
+ except NoWireContext:
+ return None
if isinstance(ctx, GenericSessionContext):
ids = (ctx.channel_id, ctx.session_id.to_bytes(1, "big"))
return ids
diff --git a/tests/persistence_tests/test_shamir_persistence.py b/tests/persistence_tests/test_shamir_persistence.py
index 3b224ba5f..72b74d9d1 100644
--- a/tests/persistence_tests/test_shamir_persistence.py
+++ b/tests/persistence_tests/test_shamir_persistence.py
@@ -83,6 +83,9 @@ def test_abort(core_emulator: Emulator):
common.go_next(debug)
assert debug.read_layout().main_component() == "Homescreen"
+
+ # create a new client, since the existing THP channel state has been wiped
+ device_handler = BackgroundDeviceHandler(core_emulator.client.get_new_client())
features = device_handler.features()
assert features.recovery_status == RecoveryStatus.Nothing
diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json
index 3c29f05e9..a018e3f2f 100644
--- a/tests/ui_tests/fixtures.json
+++ b/tests/ui_tests/fixtures.json
@@ -38007,7 +38007,7 @@
"T3W1_en_test_safety_checks.py::test_safety_checks_level_after_reboot[SafetyCheckLevel.PromptAlways--081810a6": "9982d5dec3129d225cc9d6960fe6d633ed03c6ffdc602105458947a8039c9bfc",
"T3W1_en_test_safety_checks.py::test_safety_checks_level_after_reboot[SafetyCheckLevel.PromptTempora-b3d21f4a": "766ef2d4a528f8538eec487932e02d3ae4ec7ec3472dbfff6aced38946b86d5f",
"T3W1_en_test_safety_checks.py::test_safety_checks_level_after_reboot[SafetyCheckLevel.Strict-Safety-f1ff9c26": "36cb9b759059efec753d42dbd6406548deec14f5db3ef2a770b04ed8fcf5f45e",
-"T3W1_en_test_shamir_persistence.py::test_abort": "87f078b25d5dbc5fe9b62e997b1a133abd3b54c21d14673c4b28f0e76de8742c",
+"T3W1_en_test_shamir_persistence.py::test_abort": "2a23efc9b32126d114c0b9faaa7a607c8238846f7ec2e3a450be88f610952dcb",
"T3W1_en_test_shamir_persistence.py::test_recovery_multiple_resets": "9d452f0522919237be208206caacaa58d37717925f12238fb986e152b7565954",
"T3W1_en_test_shamir_persistence.py::test_recovery_single_reset": "ad52279219dd18e9eec8aa3962c09b02d84aa373080b6dccaca4ca08abf71ac5",
"T3W1_en_test_wipe_code.py::test_wipe_code_activate_core": "c55bd8879c3ca3949fd937e66f1ca78115aae6ab94aa17fafcb1b07f4ed2309d"
Why this scored 30/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.