From 9bc57972a01435d7c7c7f2cb6def02b347bcfa7e Mon Sep 17 00:00:00 2001 From: Michael Farrell Date: Thu, 2 Jul 2026 16:41:14 +1000 Subject: [PATCH 1/2] linux: add a read timeout for USB HID devices (https://bugzilla.mozilla.org/show_bug.cgi?id=1958831) --- src/transport/linux/device.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/transport/linux/device.rs b/src/transport/linux/device.rs index 6abd462d..734563cc 100644 --- a/src/transport/linux/device.rs +++ b/src/transport/linux/device.rs @@ -9,7 +9,7 @@ use crate::transport::hid::HIDDevice; use crate::transport::platform::{hidraw, monitor}; use crate::transport::{FidoDevice, FidoProtocol, HIDError, SharedSecret}; use crate::u2ftypes::U2FDeviceInfo; -use crate::util::from_unix_result; +use crate::util::{from_unix_result, io_err}; use std::fs::OpenOptions; use std::hash::{Hash, Hasher}; use std::io; @@ -17,6 +17,20 @@ use std::io::{Read, Write}; use std::os::unix::io::AsRawFd; use std::path::PathBuf; +/// USB HID read timeout, in milliseconds. +/// +/// This is needed to prevent devices (particularly virtual `uhid` bridge devices) that *don't* +/// respond in a timely manner from [causing the browser to hang][0]. +/// +/// [CTAP 2.3 ยง11.2.9.1.7][1] says devices SHOULD send `CTAPHID_KEEPALIVE` every 100ms while +/// processing a `CTAPHID_MSG` or `CTAPHID_CBOR`. This gives devices 2 seconds to send a +/// `CTAPHID_KEEPALIVE`. [`HIDDevice::sendrecv`][] lets an application interrupt an authenticator +/// sending `CTAPHID_KEEPALIVE` forever. +/// +/// [0]: https://bugzilla.mozilla.org/show_bug.cgi?id=1958831 +/// [1]: https://fidoalliance.og/specs/fido-v2.3-ps-20260226/fido-client-to-authenticator-protocol-v2.3-ps-20260226.html#usb-hid-keepalive +const READ_TIMEOUT: i32 = 2000; + #[derive(Debug)] pub struct Device { path: PathBuf, @@ -50,6 +64,18 @@ impl Hash for Device { impl Read for Device { fn read(&mut self, buf: &mut [u8]) -> io::Result { + // Check that we can actually read from the device. + let mut pfd: libc::pollfd = unsafe { std::mem::zeroed() }; + pfd.fd = self.fd.as_raw_fd(); + pfd.events = libc::POLLIN; + let nfds = unsafe { libc::poll(&mut pfd, 1, READ_TIMEOUT) }; + if nfds == -1 { + return Err(io::Error::last_os_error()); + } + if nfds == 0 { + return Err(io_err("no response from device")); + } + let bufp = buf.as_mut_ptr() as *mut libc::c_void; let rv = unsafe { libc::read(self.fd.as_raw_fd(), bufp, buf.len()) }; from_unix_result(rv as usize) From 1a2bce038df65ea95e4377729fc8e607887be892 Mon Sep 17 00:00:00 2001 From: Michael Farrell Date: Wed, 15 Jul 2026 10:25:54 +1000 Subject: [PATCH 2/2] linux: use literal `libc::pollfd` construction, handle more `poll()` errors --- src/transport/linux/device.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/transport/linux/device.rs b/src/transport/linux/device.rs index 734563cc..736bddd6 100644 --- a/src/transport/linux/device.rs +++ b/src/transport/linux/device.rs @@ -65,14 +65,19 @@ impl Hash for Device { impl Read for Device { fn read(&mut self, buf: &mut [u8]) -> io::Result { // Check that we can actually read from the device. - let mut pfd: libc::pollfd = unsafe { std::mem::zeroed() }; - pfd.fd = self.fd.as_raw_fd(); - pfd.events = libc::POLLIN; + let mut pfd = libc::pollfd { + fd: self.fd.as_raw_fd(), + events: libc::POLLIN, + revents: 0, + }; let nfds = unsafe { libc::poll(&mut pfd, 1, READ_TIMEOUT) }; - if nfds == -1 { + if nfds == -1 || pfd.revents & libc::POLLERR != 0 { return Err(io::Error::last_os_error()); } - if nfds == 0 { + if pfd.revents & libc::POLLNVAL != 0 { + return Err(io::Error::from_raw_os_error(libc::EBADF)); + } + if nfds == 0 || pfd.revents & libc::POLLIN == 0 { return Err(io_err("no response from device")); }