refactor(core): drop `USBIF.write_blocking()` and simplify FIDO2 keep-alive sending
What changed, and why it matters
This commit is a small internal cleanup in the Trezor hardware wallet's FIDO2 (WebAuthn) code. It removes an unused 'blocking write' USB function and replaces a general synchronous send routine with a simpler one that only sends one-byte 'still processing' keep-alive messages. There is no obvious security bug being fixed; it appears to be a refactoring change.
No immediate action required. Treat as routine refactoring. If auditing, verify that keep-alive messages are still emitted correctly during long-running FIDO2 operations to avoid host-side timeouts.
Security signals we found
Removal of unused blocking USB write path reduces attack surface
No change to FIDO2 command parsing, signature generation, or credential storage logic
No bounds-checking or input-validation changes observed
Commit message explicitly frames change as refactoring with no changelog entry
Evidence from the diff
The patch deletes USBIF.write_blocking() from the C USB interface module and its Python stub, then renames send_cmd_sync() to send_keepalive_sync() and removes continuation-frame handling because keep-alive payloads are always one byte and fit in a single HID init packet. All call sites now pass the CID and status directly rather than constructing a full Cmd. The change reduces code surface but does not alter cryptographic or authorization behavior.
Changed components
core/embed/upymod/modtrezorio/modtrezorio-usb-if.hcore/mocks/generated/trezorio/__init__.pyicore/src/apps/webauthn/fido2.pyInspect captured patch +8 / −51
### core/embed/upymod/modtrezorio/modtrezorio-usb-if.h
@@ -94,29 +94,6 @@ static mp_obj_t mod_trezorio_USBIF_write(mp_obj_t self, mp_obj_t msg) {
static MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorio_USBIF_write_obj,
mod_trezorio_USBIF_write);
-/// def write_blocking(self, msg: AnyBytes, timeout_ms: int) -> int:
-/// """
-/// Sends message using USB interface.
-/// """
-static mp_obj_t mod_trezorio_USBIF_write_blocking(mp_obj_t self, mp_obj_t msg,
- mp_obj_t timeout_ms) {
- mp_obj_USBIF_t *o = MP_OBJ_TO_PTR(self);
- mp_buffer_info_t buf = {0};
- mp_get_buffer_raise(msg, &buf, MP_BUFFER_READ);
-
- if (buf.len != USB_PACKET_LEN) {
- mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid buffer length"));
- }
-
- uint32_t timeout = trezor_obj_get_uint(timeout_ms);
-
- ssize_t r = syshandle_write_blocking(o->handle, buf.buf, buf.len, timeout);
-
- return MP_OBJ_NEW_SMALL_INT(r);
-}
-static MP_DEFINE_CONST_FUN_OBJ_3(mod_trezorio_USBIF_write_blocking_obj,
- mod_trezorio_USBIF_write_blocking);
-
/// def read(self, buf: bytearray, offset: int = 0) -> int:
/// """
/// Reads message using USB interface
@@ -168,8 +145,6 @@ static const mp_rom_map_elem_t mod_trezorio_USBIF_locals_dict_table[] = {
{MP_ROM_QSTR(MP_QSTR_iface_num),
MP_ROM_PTR(&mod_trezorio_USBIF_iface_num_obj)},
{MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mod_trezorio_USBIF_write_obj)},
- {MP_ROM_QSTR(MP_QSTR_write_blocking),
- MP_ROM_PTR(&mod_trezorio_USBIF_write_blocking_obj)},
{MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mod_trezorio_USBIF_read_obj)},
{MP_ROM_QSTR(MP_QSTR_RX_PACKET_LEN), MP_ROM_INT(USB_PACKET_LEN)},
{MP_ROM_QSTR(MP_QSTR_TX_PACKET_LEN), MP_ROM_INT(USB_PACKET_LEN)},
### core/mocks/generated/trezorio/__init__.pyi
@@ -77,11 +77,6 @@ class USBIF:
Sends message using USB interface.
"""
- def write_blocking(self, msg: AnyBytes, timeout_ms: int) -> int:
- """
- Sends message using USB interface.
- """
-
def read(self, buf: bytearray, offset: int = 0) -> int:
"""
Reads message using USB interface
### core/src/apps/webauthn/fido2.py
@@ -519,33 +519,20 @@ async def send_cmd(cmd: Cmd, iface: HID) -> None:
seq += 1
-def send_cmd_sync(cmd: Cmd, iface: HID) -> None:
+def send_keepalive_sync(cid: int, status: int, iface: HID) -> None:
+ cmd = cmd_keepalive(cid, status)
init_desc = frame_init()
- cont_desc = frame_cont()
- offset = 0
- seq = 0
datalen = len(cmd.data)
buf, frm = make_struct(init_desc)
frm.cid = cmd.cid
frm.cmd = cmd.cmd
frm.bcnt = datalen
- offset += utils.memcpy(frm.data, 0, cmd.data, offset, datalen)
+ offset = utils.memcpy(frm.data, 0, cmd.data, 0, datalen)
+ assert offset == datalen # 1-byte payload fits into one USB packet
iface.write(buf)
- if offset < datalen:
- frm = overlay_struct(buf, cont_desc)
-
- while offset < datalen:
- frm.seq = seq
- copied = utils.memcpy(frm.data, 0, cmd.data, offset, datalen)
- offset += copied
- if copied < _FRAME_CONT_SIZE:
- frm.data[copied:] = bytearray(_FRAME_CONT_SIZE - copied)
- iface.write_blocking(buf, 1000)
- seq += 1
-
async def handle_reports(iface: HID) -> None:
dialog_mgr = DialogManager(iface)
@@ -571,7 +558,7 @@ def __init__(self, cid: int, iface: HID) -> None:
self.iface = iface
def __call__(self) -> None:
- send_cmd_sync(cmd_keepalive(self.cid, _KEEPALIVE_STATUS_PROCESSING), self.iface)
+ send_keepalive_sync(self.cid, _KEEPALIVE_STATUS_PROCESSING, self.iface)
async def verify_user(keepalive_callback: KeepaliveCallback) -> bool:
@@ -843,14 +830,14 @@ async def on_confirm(self) -> None:
cid = self.cid # local_cache_attribute
self._cred.generate_id()
- send_cmd_sync(cmd_keepalive(cid, _KEEPALIVE_STATUS_PROCESSING), self.iface)
+ 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_cmd_sync(cmd_keepalive(cid, _KEEPALIVE_STATUS_PROCESSING), self.iface)
+ 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)
@@ -911,7 +898,7 @@ async def on_confirm(self) -> None:
assert self._selected_cred is not None
try:
- send_cmd_sync(cmd_keepalive(cid, _KEEPALIVE_STATUS_PROCESSING), self.iface)
+ 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 18/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.