fix(core): don't reuse `_TIMEOUT_ERROR` exception object
What changed, and why it matters
This commit fixes a subtle bug in the Trezor device's task scheduler where the same exception object was being reused for every timeout. In Python, exception objects carry state (such as a traceback). Reusing the same object across many tasks and timeouts could cause stale traceback or context information to leak between unrelated operations, or trigger unexpected behavior if code catches and inspects the exception. The fix creates a fresh Timeout exception each time one is needed.
Treat as a low-to-moderate reliability/security hardening fix. Include in firmware release notes as a stability improvement. No immediate incident response required absent evidence of active exploitation.
Security signals we found
Reused mutable exception object across concurrent tasks
Potential cross-task state leakage via exception traceback/context
Embedded scheduler/runtime hardening
Follow-up fix to prior PR (#5523)
Evidence from the diff
In core/src/trezor/loop.py, the global singleton _TIMEOUT_ERROR = Timeout() was removed. The wait syscall now passes a newly instantiated Timeout() to schedule() instead of the shared object. Reused exception objects can accumulate traceback and context state when raised/caught, which on a constrained embedded runtime may lead to information leakage across coroutines, incorrect exception identity checks, or memory/gc side effects. The change follows PR #5523 and is marked [no changelog].
Changed components
core/src/trezor/loop.pywait syscall / task schedulingTimeout exception handlingInspect captured patch +1 / −4
diff --git a/core/src/trezor/loop.py b/core/src/trezor/loop.py
index ae4d6843b..385998fb7 100644
--- a/core/src/trezor/loop.py
+++ b/core/src/trezor/loop.py
@@ -201,9 +201,6 @@ class Timeout(Exception):
pass
-_TIMEOUT_ERROR = Timeout()
-
-
class sleep(Syscall):
"""Pause current task and resume it after given delay.
@@ -247,7 +244,7 @@ class wait(Syscall):
pause(self, self.msg_iface)
if self.timeout_ms is not None:
deadline = utime.ticks_add(utime.ticks_ms(), self.timeout_ms)
- schedule(self, _TIMEOUT_ERROR, deadline)
+ schedule(self, Timeout(), deadline)
def send(self, __value: Any) -> Any:
assert self.task is not None
Why this scored 35/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.