devices/usb/backend/fido_backend/
fido_passthrough.rs

1// Copyright 2024 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::collections::VecDeque;
6use std::io::Error as IOError;
7use std::io::Read;
8use std::sync::Arc;
9use std::sync::RwLock;
10
11use base::debug;
12use base::error;
13use base::AsRawDescriptor;
14use base::Event;
15use base::RawDescriptor;
16use base::WorkerThread;
17use sync::Mutex;
18use usb_util::parse_usbfs_descriptors;
19use usb_util::ConfigDescriptorTree;
20use usb_util::ControlRequestDataPhaseTransferDirection;
21use usb_util::ControlRequestRecipient;
22use usb_util::ControlRequestType;
23use usb_util::DescriptorType;
24use usb_util::DeviceDescriptorTree;
25use usb_util::DeviceSpeed;
26use usb_util::EndpointDirection;
27use usb_util::EndpointType;
28use usb_util::Error as UsbUtilError;
29use usb_util::TransferBuffer;
30use usb_util::TransferStatus;
31use usb_util::UsbRequestSetup;
32use zerocopy::FromBytes;
33use zerocopy::IntoBytes;
34
35use crate::usb::backend::device::BackendDevice;
36use crate::usb::backend::device::DeviceState;
37use crate::usb::backend::endpoint::ControlEndpointState;
38use crate::usb::backend::endpoint::UsbEndpoint;
39use crate::usb::backend::error::Error as BackendError;
40use crate::usb::backend::error::Result as BackendResult;
41use crate::usb::backend::fido_backend::constants;
42use crate::usb::backend::fido_backend::error::Error;
43use crate::usb::backend::fido_backend::error::Result;
44use crate::usb::backend::fido_backend::fido_device::FidoDevice;
45use crate::usb::backend::fido_backend::poll_thread::poll_for_pending_packets;
46use crate::usb::backend::fido_backend::transfer::FidoTransfer;
47use crate::usb::backend::fido_backend::transfer::FidoTransferHandle;
48use crate::usb::backend::transfer::BackendTransferHandle;
49use crate::usb::backend::transfer::BackendTransferType;
50use crate::usb::backend::transfer::ControlTransferState;
51use crate::usb::backend::transfer::GenericTransferHandle;
52use crate::usb::xhci::xhci_backend_device::BackendType;
53use crate::usb::xhci::xhci_backend_device::UsbDeviceAddress;
54use crate::usb::xhci::xhci_backend_device::XhciBackendDevice;
55use crate::utils::AsyncJobQueue;
56use crate::utils::EventLoop;
57
58/// Host-level fido passthrough device that handles USB operations and relays them to the
59/// appropriate virtual fido device.
60pub struct FidoPassthroughDevice {
61    /// The virtual FIDO device implementation.
62    device: Arc<Mutex<FidoDevice>>,
63    /// The state of the device as seen by the backend provider.
64    state: Arc<RwLock<DeviceState>>,
65    /// The state of the control transfer exchange with the xhci layer.
66    control_transfer_state: Arc<RwLock<ControlTransferState>>,
67    transfer_job_queue: Arc<AsyncJobQueue>,
68    kill_evt: Event,
69    worker_thread: Option<WorkerThread<()>>,
70    pending_in_transfers:
71        Arc<Mutex<VecDeque<(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>)>>>,
72}
73
74impl FidoPassthroughDevice {
75    pub fn new(
76        device: Arc<Mutex<FidoDevice>>,
77        state: DeviceState,
78        event_loop: Arc<EventLoop>,
79    ) -> Result<Self> {
80        let control_transfer_state = ControlTransferState {
81            ctl_ep_state: ControlEndpointState::SetupStage,
82            control_request_setup: UsbRequestSetup::new(0, 0, 0, 0, 0),
83            data_stage_transfer: None,
84        };
85        let job_queue = AsyncJobQueue::init(&event_loop).map_err(Error::StartAsyncFidoQueue)?;
86        Ok(FidoPassthroughDevice {
87            device,
88            state: Arc::new(RwLock::new(state)),
89            control_transfer_state: Arc::new(RwLock::new(control_transfer_state)),
90            transfer_job_queue: job_queue,
91            kill_evt: Event::new().unwrap(),
92            worker_thread: None,
93            pending_in_transfers: Arc::new(Mutex::new(VecDeque::new())),
94        })
95    }
96
97    /// This function is called from the low-level event handler when the monitored `fd` is ready
98    /// to transmit data from the host to the guest.
99    pub fn read_hidraw_file(&mut self) -> Result<()> {
100        let mut device = self.device.lock();
101        // Device has already stopped working, just return early.
102        if device.is_device_lost {
103            return Ok(());
104        }
105        if !device.is_active {
106            // We should NEVER be polling on the fd and wake up if no transactions have been
107            // initiated from the guest first.
108            error!("Fido device received fd poll event from inactive device. This is a bug.");
109            return Err(Error::InconsistentFidoDeviceState);
110        }
111
112        let mut packet = vec![0; constants::U2FHID_PACKET_SIZE * 2];
113
114        if device.guest_key.lock().pending_in_packets.len() >= constants::U2FHID_MAX_IN_PENDING {
115            return Err(Error::PendingInQueueFull);
116        }
117
118        let read_result = device.fd.lock().read(&mut packet);
119        match read_result {
120            Ok(n) => {
121                // We read too much, the device is misbehaving
122                if n != constants::U2FHID_PACKET_SIZE {
123                    return Err(Error::ReadHidrawDevice(IOError::other(format!(
124                        "Read too many bytes ({n}), the hidraw device is misbehaving."
125                    ))));
126                }
127                // This is safe because we just checked the size of n is exactly U2FHID_PACKET_SIZE
128                device
129                    .recv_from_host(&packet[..constants::U2FHID_PACKET_SIZE].try_into().unwrap())?;
130            }
131            Err(e) => {
132                error!("U2F hidraw read error: {e:#}, resetting and detaching device",);
133                device.set_active(false);
134                device.is_device_lost = true;
135                return Err(Error::ReadHidrawDevice(e));
136            }
137        }
138        Ok(())
139    }
140
141    /// This function is called by a queued job to handle all communication related to USB control
142    /// transfer packets between the guest and the virtual security key.
143    pub fn handle_control(
144        transfer: &mut FidoTransfer,
145        device: &Arc<Mutex<FidoDevice>>,
146    ) -> Result<()> {
147        transfer.actual_length = 0;
148        let request_setup = match &transfer.buffer {
149            TransferBuffer::Vector(v) => {
150                UsbRequestSetup::read_from_prefix(v)
151                    .map_err(|_| Error::InvalidDataBufferSize)?
152                    .0
153            }
154            _ => {
155                return Err(Error::UnsupportedTransferBufferType);
156            }
157        };
158
159        let mut request_setup_out = request_setup.as_bytes().to_vec();
160        let is_device_to_host =
161            request_setup.get_direction() == ControlRequestDataPhaseTransferDirection::DeviceToHost;
162        let descriptor_type = (request_setup.value >> 8) as u8;
163
164        // Get Device Descriptor request
165        if descriptor_type == (DescriptorType::Device as u8) && is_device_to_host {
166            // If the descriptor is larger than the actual requested data, we only allocate space
167            // for the request size. This is common for USB3 control setup to request only the
168            // initial 8 bytes instead of the full descriptor.
169            let buf_size = std::cmp::min(
170                request_setup.length.into(),
171                constants::U2FHID_DEVICE_DESC.len(),
172            );
173            let mut buffer: Vec<u8> = constants::U2FHID_DEVICE_DESC[..buf_size].to_vec();
174            transfer.actual_length = buffer.len();
175            request_setup_out.append(&mut buffer);
176        }
177
178        if request_setup.get_recipient() == ControlRequestRecipient::Interface {
179            // It's a request for the HID report descriptor
180            if is_device_to_host && descriptor_type == constants::HID_GET_REPORT_DESC {
181                let mut buffer: Vec<u8> = constants::HID_REPORT_DESC.to_vec();
182                transfer.actual_length = buffer.len();
183                request_setup_out.append(&mut buffer);
184            }
185        }
186
187        if request_setup.get_type() == ControlRequestType::Class {
188            match request_setup.request {
189                constants::HID_GET_IDLE => {
190                    let mut buffer: Vec<u8> = vec![0u8, 1];
191                    buffer[0] = device.lock().guest_key.lock().idle;
192                    transfer.actual_length = 1;
193                    request_setup_out.append(&mut buffer);
194                }
195                constants::HID_SET_IDLE => {
196                    device.lock().guest_key.lock().idle = (request_setup.value >> 8) as u8;
197                }
198                _ => {
199                    debug!(
200                        "Received unsupported setup request code of Class type: {}",
201                        request_setup.request
202                    );
203                }
204            }
205        }
206
207        // Store the response
208        transfer.buffer = TransferBuffer::Vector(request_setup_out);
209        Ok(())
210    }
211
212    /// This function is called by a queued job to handle all USB OUT requests from the guest down
213    /// to the host by writing the given `FidoTransfer` data into the hidraw file.
214    pub fn handle_interrupt_out(
215        transfer: &mut FidoTransfer,
216        device: &Arc<Mutex<FidoDevice>>,
217    ) -> Result<()> {
218        let mut packet = [0u8; constants::U2FHID_PACKET_SIZE];
219        let buffer = match &transfer.buffer {
220            TransferBuffer::Vector(v) => v,
221            _ => {
222                return Err(Error::UnsupportedTransferBufferType);
223            }
224        };
225        if buffer.len() > constants::U2FHID_PACKET_SIZE {
226            error!(
227                "Buffer size is bigger than u2f-hid packet size: {}",
228                buffer.len()
229            );
230            return Err(Error::InvalidDataBufferSize);
231        }
232        packet.copy_from_slice(buffer);
233        let written = device.lock().recv_from_guest(&packet)?;
234        transfer.actual_length = written;
235        Ok(())
236    }
237}
238
239impl Drop for FidoPassthroughDevice {
240    fn drop(&mut self) {
241        self.device.lock().is_device_lost = true;
242        if let Err(e) = self.kill_evt.signal() {
243            error!(
244                "Failed to send signal to stop poll worker thread, \
245                it might have already stopped. {e:#}"
246            );
247        }
248    }
249}
250
251impl AsRawDescriptor for FidoPassthroughDevice {
252    fn as_raw_descriptor(&self) -> RawDescriptor {
253        self.device.lock().as_raw_descriptor()
254    }
255}
256
257impl BackendDevice for FidoPassthroughDevice {
258    fn submit_backend_transfer(
259        &mut self,
260        transfer: BackendTransferType,
261    ) -> BackendResult<BackendTransferHandle> {
262        let transfer = match transfer {
263            BackendTransferType::FidoDevice(transfer) => transfer,
264            _ => return Err(BackendError::MalformedBackendTransfer),
265        };
266
267        let endpoint = transfer.endpoint;
268        let arc_transfer = Arc::new(Mutex::new(Some(transfer)));
269        let cancel_handle = FidoTransferHandle {
270            weak_transfer: Arc::downgrade(&arc_transfer),
271        };
272
273        match endpoint {
274            constants::U2FHID_CONTROL_ENDPOINT => {
275                let arc_transfer_local = arc_transfer.clone();
276                let fido_device = self.device.clone();
277                self.transfer_job_queue
278                    .queue_job(move || {
279                        let mut lock = arc_transfer_local.lock();
280                        match lock.take() {
281                            Some(mut transfer) => {
282                                if let Err(e) = FidoPassthroughDevice::handle_control(
283                                    &mut transfer,
284                                    &fido_device,
285                                ) {
286                                    error!(
287                                        "Fido device handle control failed, cancelling transfer:\
288                                        {e:#}"
289                                    );
290                                    drop(lock);
291                                    if let Err(e) = cancel_handle.cancel() {
292                                        error!(
293                                            "Failed to cancel transfer, dropping request: {e:#}"
294                                        );
295                                        return;
296                                    }
297                                }
298                                transfer.complete_transfer();
299                            }
300                            None => {
301                                error!(
302                                    "USB transfer disappeared in handle_control. Dropping request."
303                                );
304                            }
305                        }
306                    })
307                    .map_err(BackendError::QueueAsyncJob)?;
308            }
309            constants::U2FHID_OUT_ENDPOINT => {
310                let arc_transfer_local = arc_transfer.clone();
311                let fido_device = self.device.clone();
312                self.transfer_job_queue
313                    .queue_job(move || {
314                        let mut lock = arc_transfer_local.lock();
315                        match lock.take() {
316                            Some(mut transfer) => {
317                                if let Err(e) = FidoPassthroughDevice::handle_interrupt_out(
318                                    &mut transfer,
319                                    &fido_device,
320                                ) {
321                                    error!(
322                                        "Fido device handle interrupt out failed,\
323                                        cancelling transfer: {e:#}"
324                                    );
325                                    drop(lock);
326                                    if let Err(e) = cancel_handle.cancel() {
327                                        error!(
328                                            "Failed to cancel transfer, dropping request: {e:#}"
329                                        );
330                                        return;
331                                    }
332                                }
333                                transfer.complete_transfer();
334                            }
335                            None => {
336                                error!("Interrupt out transfer disappeared. Dropping request.");
337                            }
338                        }
339                    })
340                    .map_err(BackendError::QueueAsyncJob)?;
341            }
342            constants::U2FHID_IN_ENDPOINT => {
343                let handle = FidoTransferHandle {
344                    weak_transfer: Arc::downgrade(&arc_transfer.clone()),
345                };
346                self.pending_in_transfers
347                    .lock()
348                    .push_back((handle, arc_transfer.clone()));
349
350                // Make sure to arm the timer for both transfer and host packet polling as we wait
351                // for transaction requests to be fulfilled by the host or xhci transfer to time
352                // out.
353                if let Err(e) = self.device.lock().guest_key.lock().timer.arm() {
354                    error!("Unable to start U2F guest key timer. U2F packets may be lost. {e:#}");
355                }
356                if let Err(e) = self.device.lock().transfer_timer.arm() {
357                    error!("Unable to start transfer poll timer. Transfers might stall. {e:#}");
358                }
359            }
360            _ => {
361                error!("Wrong endpoint requested: {endpoint}");
362                return Err(BackendError::MalformedBackendTransfer);
363            }
364        }
365
366        // Start the worker thread if it hasn't been created yet
367        if self.worker_thread.is_none()
368            && (endpoint == constants::U2FHID_IN_ENDPOINT
369                || endpoint == constants::U2FHID_OUT_ENDPOINT)
370        {
371            let device = self.device.clone();
372            let pending_in_transfers = self.pending_in_transfers.clone();
373            self.worker_thread = Some(WorkerThread::start("fido poll thread", move |kill_evt| {
374                if let Err(e) = poll_for_pending_packets(device, pending_in_transfers, kill_evt) {
375                    error!("Poll worker thread errored: {e:#}");
376                }
377            }));
378        }
379
380        let cancel_handle = FidoTransferHandle {
381            weak_transfer: Arc::downgrade(&arc_transfer),
382        };
383        Ok(BackendTransferHandle::new(cancel_handle))
384    }
385
386    fn detach_event_handler(&self, _event_loop: &Arc<EventLoop>) -> BackendResult<()> {
387        self.device.lock().set_active(false);
388        Ok(())
389    }
390
391    fn request_transfer_buffer(&mut self, size: usize) -> TransferBuffer {
392        TransferBuffer::Vector(vec![0u8; size])
393    }
394
395    fn build_bulk_transfer(
396        &mut self,
397        _ep_addr: u8,
398        _transfer_buffer: TransferBuffer,
399        _stream_id: Option<u16>,
400    ) -> BackendResult<BackendTransferType> {
401        // Fido devices don't support bulk transfer requests
402        Err(BackendError::MalformedBackendTransfer)
403    }
404
405    fn build_interrupt_transfer(
406        &mut self,
407        ep_addr: u8,
408        transfer_buffer: TransferBuffer,
409    ) -> BackendResult<BackendTransferType> {
410        Ok(BackendTransferType::FidoDevice(FidoTransfer::new(
411            ep_addr,
412            transfer_buffer,
413        )))
414    }
415
416    fn build_isochronous_transfer(
417        &mut self,
418        _ep_addr: u8,
419        _transfer_buffer: TransferBuffer,
420        _packet_size: u32,
421    ) -> BackendResult<BackendTransferType> {
422        // Fido devices don't support isochronous transfer requests
423        Err(BackendError::MalformedBackendTransfer)
424    }
425
426    fn get_control_transfer_state(&mut self) -> Arc<RwLock<ControlTransferState>> {
427        self.control_transfer_state.clone()
428    }
429
430    fn get_device_state(&mut self) -> Arc<RwLock<DeviceState>> {
431        self.state.clone()
432    }
433
434    fn get_active_config_descriptor(&mut self) -> BackendResult<ConfigDescriptorTree> {
435        // There is only a config descriptor for u2f virtual keys.
436        self.get_config_descriptor_by_index(0)
437    }
438
439    fn get_config_descriptor(&mut self, config: u8) -> BackendResult<ConfigDescriptorTree> {
440        let device_descriptor = self.get_device_descriptor_tree()?;
441        if let Some(config_descriptor) = device_descriptor.get_config_descriptor(config) {
442            return Ok(config_descriptor.clone());
443        }
444        Err(BackendError::GetConfigDescriptor(
445            UsbUtilError::DescriptorParse,
446        ))
447    }
448
449    fn get_config_descriptor_by_index(
450        &mut self,
451        config_index: u8,
452    ) -> BackendResult<ConfigDescriptorTree> {
453        let device_descriptor = self.get_device_descriptor_tree()?;
454        if let Some(config_descriptor) =
455            device_descriptor.get_config_descriptor_by_index(config_index)
456        {
457            return Ok(config_descriptor.clone());
458        }
459        Err(BackendError::GetConfigDescriptor(
460            UsbUtilError::DescriptorParse,
461        ))
462    }
463
464    fn get_device_descriptor_tree(&mut self) -> BackendResult<DeviceDescriptorTree> {
465        // Skip the first two fields of length and descriptor type as we don't need them in our
466        // DeviceDescriptor structure.
467        let mut descbuf: Vec<u8> = constants::U2FHID_DEVICE_DESC.to_vec();
468        let mut configbuf: Vec<u8> = constants::U2FHID_CONFIG_DESC.to_vec();
469        descbuf.append(&mut configbuf);
470        parse_usbfs_descriptors(&descbuf).map_err(BackendError::GetDeviceDescriptor)
471    }
472
473    fn get_active_configuration(&mut self) -> BackendResult<u8> {
474        let descriptor_tree = self.get_device_descriptor_tree()?;
475        if descriptor_tree.bNumConfigurations != 1 {
476            error!(
477                "Fido devices should only have one configuration, found {}",
478                descriptor_tree.bNumConfigurations
479            );
480        } else if let Some(config_descriptor) = descriptor_tree.get_config_descriptor_by_index(0) {
481            return Ok(config_descriptor.bConfigurationValue);
482        }
483        Err(BackendError::GetActiveConfig(UsbUtilError::DescriptorParse))
484    }
485
486    fn set_active_configuration(&mut self, config: u8) -> BackendResult<()> {
487        // Fido devices only have one configuration so we should do nothing here.
488        // Return an error if the configuration number is unexpected.
489        if config != 0 {
490            error!(
491                "Requested to set fido active configuration of {config}, but only 0 is allowed."
492            );
493            return Err(BackendError::BadBackendProviderState);
494        }
495        Ok(())
496    }
497
498    fn clear_feature(&mut self, _value: u16, _index: u16) -> BackendResult<TransferStatus> {
499        // Nothing to do here, just return.
500        Ok(TransferStatus::Completed)
501    }
502
503    fn create_endpoints(&mut self, _config_descriptor: &ConfigDescriptorTree) -> BackendResult<()> {
504        let mut endpoints = Vec::new();
505        let device_state = self.get_device_state();
506        // We ignore the config descriptor because u2f-hid endpoints are already defined by the
507        // protocol and are unchanging.
508        // Endpoint 1 (OUT)
509        endpoints.push(UsbEndpoint::new(
510            device_state.read().unwrap().fail_handle.clone(),
511            device_state.read().unwrap().job_queue.clone(),
512            1,
513            EndpointDirection::HostToDevice,
514            EndpointType::Interrupt,
515        ));
516        // Endpoint 1 (IN)
517        endpoints.push(UsbEndpoint::new(
518            device_state.read().unwrap().fail_handle.clone(),
519            device_state.read().unwrap().job_queue.clone(),
520            1,
521            EndpointDirection::DeviceToHost,
522            EndpointType::Interrupt,
523        ));
524        device_state.write().unwrap().endpoints = endpoints;
525        Ok(())
526    }
527}
528
529impl XhciBackendDevice for FidoPassthroughDevice {
530    fn get_backend_type(&self) -> BackendType {
531        BackendType::Usb2
532    }
533
534    fn get_vid(&self) -> u16 {
535        // Google vendor ID
536        0x18d1
537    }
538
539    fn get_pid(&self) -> u16 {
540        // Unique Product ID
541        0xf1d0
542    }
543
544    fn set_address(&mut self, _address: UsbDeviceAddress) {
545        // Nothing to do here
546    }
547
548    fn reset(&mut self) -> BackendResult<()> {
549        let mut device_lock = self.device.lock();
550        device_lock.set_active(false);
551        device_lock.guest_key.lock().reset();
552        device_lock.transaction_manager.lock().reset();
553        Ok(())
554    }
555
556    fn get_speed(&self) -> Option<DeviceSpeed> {
557        Some(DeviceSpeed::Full)
558    }
559
560    fn alloc_streams(&self, _ep: u8, _num_streams: u16) -> BackendResult<()> {
561        // FIDO devices don't support bulk/streams so we ignore this request.
562        Ok(())
563    }
564
565    fn free_streams(&self, _ep: u8) -> BackendResult<()> {
566        // FIDO devices don't support bulk/streams so we ignore this request.
567        Ok(())
568    }
569
570    fn stop(&mut self) {
571        // Transition the FIDO device into inactive mode and mark device as lost.
572        // The FIDO device cannot error on reset so we can unwrap safely.
573        self.reset().unwrap();
574        self.device.lock().is_device_lost = true;
575    }
576}