fix(core/fido): don't fail if `_KEEPALIVE_STATUS_PROCESSING` is blocked
What changed, and why it matters
This update fixes a bug in the Trezor hardware wallet's FIDO2/WebAuthn support. Previously, when the device was busy and tried to send a 'still processing' keepalive message to the computer, it would crash the whole FIDO operation if the USB send buffer happened to be full. Now it simply skips that single keepalive message and continues, making FIDO logins more reliable.
Treat as a reliability/hardening fix worth including in the next firmware release. No immediate advisory is required, but verify that the remaining `send_cmd()` path referenced in #7553 is similarly hardened to avoid analogous failures during CBOR command responses.
Security signals we found
Denial-of-service hardening: prevents a transient USB buffer-full condition from aborting a FIDO authentication/registration workflow
Exception boundary added around low-level USB HID write
Changelog explicitly frames the change as a fix for FIDO interaction failure
Evidence from the diff
The commit renames send_keepalive_sync to try_send_keepalive_sync and wraps the iface.write(buf) call in a try/except for OSError. If the USB HID write fails (e.g., because the outgoing buffer is full), the exception is caught and logged in debug builds instead of propagating and aborting the FIDO workflow. All call sites that send _KEEPALIVE_STATUS_PROCESSING are updated to use the new tolerant function. A related issue in fido2.send_cmd() is noted but not fixed here (#7553).
Changed components
core/src/apps/webauthn/fido2.pyFIDO2/WebAuthn keepalive handling on Trezor Model T / Core devicesInspect captured patch +12 / −6
### core/.changelog.d/7487.fixed
@@ -0,0 +1 @@
+Don't fail FIDO interaction if keepalive message is blocked.
### core/src/apps/webauthn/fido2.py
@@ -519,7 +519,7 @@ async def send_cmd(cmd: Cmd, iface: HID) -> None:
seq += 1
-def send_keepalive_sync(cid: int, status: int, iface: HID) -> None:
+def try_send_keepalive_sync(cid: int, status: int, iface: HID) -> None:
cmd = cmd_keepalive(cid, status)
init_desc = frame_init()
datalen = len(cmd.data)
@@ -531,7 +531,12 @@ def send_keepalive_sync(cid: int, status: int, iface: HID) -> None:
offset = utils.memcpy(frm.data, 0, cmd.data, 0, datalen)
assert offset == datalen # 1-byte payload fits into one USB packet
- iface.write(buf)
+ try:
+ iface.write(buf)
+ except OSError as e:
+ # Don't fail the workflow, if the USB interface is blocked.
+ if __debug__:
+ log.warning(__name__, "Keepalive %s skipped: %s", status, e)
async def handle_reports(iface: HID) -> None:
@@ -558,7 +563,7 @@ def __init__(self, cid: int, iface: HID) -> None:
self.iface = iface
def __call__(self) -> None:
- send_keepalive_sync(self.cid, _KEEPALIVE_STATUS_PROCESSING, self.iface)
+ try_send_keepalive_sync(self.cid, _KEEPALIVE_STATUS_PROCESSING, self.iface)
async def verify_user(keepalive_callback: KeepaliveCallback) -> bool:
@@ -830,14 +835,14 @@ async def on_confirm(self) -> None:
cid = self.cid # local_cache_attribute
self._cred.generate_id()
- send_keepalive_sync(cid, _KEEPALIVE_STATUS_PROCESSING, self.iface)
+ try_send_keepalive_sync(cid, _KEEPALIVE_STATUS_PROCESSING, self.iface)
response_data = _cbor_make_credential_sign(
self._client_data_hash, self._cred, self._user_verification
)
cmd = Cmd(cid, _CMD_CBOR, bytes([_ERR_NONE]) + response_data)
if self._resident:
- send_keepalive_sync(cid, _KEEPALIVE_STATUS_PROCESSING, self.iface)
+ try_send_keepalive_sync(cid, _KEEPALIVE_STATUS_PROCESSING, self.iface)
if not store_resident_credential(self._cred):
cmd = cbor_error(cid, _ERR_KEY_STORE_FULL)
await send_cmd(cmd, self.iface)
@@ -898,7 +903,7 @@ async def on_confirm(self) -> None:
assert self._selected_cred is not None
try:
- send_keepalive_sync(cid, _KEEPALIVE_STATUS_PROCESSING, self.iface)
+ try_send_keepalive_sync(cid, _KEEPALIVE_STATUS_PROCESSING, self.iface)
response_data = cbor_get_assertion_sign(
self._client_data_hash,
self._selected_cred.rp_id_hash,Why this scored 37/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.