fix(python/trezorctl): avoid using atexit for closing of transport
What changed, and why it matters
This commit fixes a crash bug in the Trezor command-line tool (trezorctl). It changes when the USB connection to the Trezor device is closed, moving cleanup from program shutdown to right after each command finishes. The old approach could trigger a segmentation fault (a hard crash) in the underlying USB library because two different cleanup routines interfered with each other at exit.
Treat as a stability/reliability fix. No immediate security response required unless further analysis shows the segfault is exploitable for denial of service or memory corruption. Users of trezorctl should update to the fixed version to avoid crashes on exit, especially when using WebUSB transport.
Security signals we found
Fixes a segmentation fault in libusb during process teardown
Removes atexit-based resource cleanup that conflicted with another atexit handler
Deterministic resource cleanup via finally block
Crash-only reliability issue, not an obvious confidentiality/integrity vulnerability
Evidence from the diff
The patch removes use of Python’s atexit to close the transport object and instead overrides Click’s Group.invoke() to close ctx.obj in a finally block after each command invocation. The commit message states the old atexit-based cleanup conflicted with an atexit callback registered inside the WebUSB transport and could cause a libusb segfault. The change ensures transport cleanup happens deterministically after command execution rather than nondeterministically during interpreter shutdown.
Changed components
python/src/trezorlib/cli/trezorctl.pyTrezorctlGroup.invoke() methodTransport object lifecycle (ctx.obj.open()/close())Inspect captured patch +7 / −5
diff --git a/python/src/trezorlib/cli/trezorctl.py b/python/src/trezorlib/cli/trezorctl.py
index 090a5963..800615b1 100755
--- a/python/src/trezorlib/cli/trezorctl.py
+++ b/python/src/trezorlib/cli/trezorctl.py
@@ -18,7 +18,6 @@
from __future__ import annotations
-import atexit
import importlib.metadata
import json
import logging
@@ -157,6 +156,13 @@ class TrezorctlGroup(AliasedGroup):
else:
return super().result_callback()
+ def invoke(self, ctx: click.Context) -> Any:
+ try:
+ return super().invoke(ctx)
+ finally:
+ if ctx.obj is not None:
+ ctx.obj.close()
+
def configure_logging(verbose: int) -> None:
if verbose:
@@ -244,10 +250,6 @@ def cli_main(
script=script,
record_dir=record,
)
- # Enumerate and open the underyling device.
- # Currently, we rely on this call to register lower-level `atexit` callbacks.
- ctx.obj.open()
- atexit.register(ctx.obj.close)
# Creating a cli function that has the right types for future usage
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.