From 0a8b5a08c2c2d3ed8954f7b4cac35ee2c9858c3f Mon Sep 17 00:00:00 2001 From: matejcik Date: Tue, 26 Feb 2019 17:31:44 +0100 Subject: [PATCH] trezorlib: workaround for a problem with Trezor One webusb when webusb version of T1 is wiped, the usb device changes serial immediately (unlike TT, which changes it after reconnect). That confuses libusb on linux, and next time the device is reset, it will insist on re-enumeration. To solve this, we leave some explanatory comments, and trigger the device reset through opening the device right after a wipe. The client instance is unusable after that, but not much we can do about it, and on next run trezorctl will behave as if nothing bad happened. --- trezorctl | 3 ++- trezorlib/device.py | 25 ++++++++++++++++++++----- trezorlib/transport/webusb.py | 9 +++++++++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/trezorctl b/trezorctl index 9c31b071e..f064922f9 100755 --- a/trezorctl +++ b/trezorctl @@ -374,7 +374,8 @@ def wipe_device(connect, bootloader): click.echo("Wiping user data!") try: - return device.wipe(connect()) + device.wipe(client) + click.echo("Device wiped") except tools.CallException as e: click.echo("Action failed: {} {}".format(*e.args)) sys.exit(3) diff --git a/trezorlib/device.py b/trezorlib/device.py index a06db8455..559c8196f 100644 --- a/trezorlib/device.py +++ b/trezorlib/device.py @@ -45,6 +45,7 @@ class TrezorDevice: @expect(proto.Success, field="message") +@session def apply_settings( client, label=None, @@ -74,6 +75,7 @@ def apply_settings( @expect(proto.Success, field="message") +@session def apply_flags(client, flags): out = client.call(proto.ApplyFlags(flags=flags)) client.init_device() # Reload Features @@ -81,6 +83,7 @@ def apply_flags(client, flags): @expect(proto.Success, field="message") +@session def change_pin(client, remove=False): ret = client.call(proto.ChangePin(remove=remove)) client.init_device() # Re-read features @@ -93,14 +96,26 @@ def set_u2f_counter(client, u2f_counter): return ret -@expect(proto.Success, field="message") -def wipe(client): - ret = client.call(proto.WipeDevice()) - client.init_device() - return ret +def wipe(client) -> bool: + """Initiate device wipe. + + Returns whether it's safe to continue using the client instance. + (see comment in `WebUsbHandle.open`) + """ + # This is not marked @session by design: the subsequent init_device should run + # in a separate session, so that when it triggers a USB error, a subsequent + # re-enumeration fixes it. See comment in WebUsbHandle.open + # XXX this should actually call the USB reset explicitly + client.call(proto.WipeDevice()) + try: + client.init_device() + return True + except Exception: + return False @expect(proto.Success, field="message") +@session def recover( client, word_count=24, diff --git a/trezorlib/transport/webusb.py b/trezorlib/transport/webusb.py index a55d76c78..1d5026fc4 100644 --- a/trezorlib/transport/webusb.py +++ b/trezorlib/transport/webusb.py @@ -53,7 +53,16 @@ class WebUsbHandle: else: args = () raise IOError("Cannot open device", *args) + self.handle.resetDevice() + # XXX this may fail with LIBUSB_ERROR_NOT_FOUND + # This will usually happen after device wipe, because USB serial has changed, + # and requires re-enumeration and reacquiring of the device. + # I haven't found a reasonable way of handling it here, or in the enumeration + # step. (In particular, it seems impossible to force libusb to re-enumerate + # explicitly, without calling device reset.) + # So now I'm just leaving it here to crash in some cases. + self.handle.claimInterface(self.interface) def close(self) -> None: