Path: blob/main/devices/src/usb/backend/fido_backend/fido_passthrough.rs
5394 views
// Copyright 2024 The ChromiumOS Authors1// Use of this source code is governed by a BSD-style license that can be2// found in the LICENSE file.34use std::collections::VecDeque;5use std::io::Error as IOError;6use std::io::Read;7use std::sync::Arc;8use std::sync::RwLock;910use base::debug;11use base::error;12use base::AsRawDescriptor;13use base::Event;14use base::RawDescriptor;15use base::WorkerThread;16use sync::Mutex;17use usb_util::parse_usbfs_descriptors;18use usb_util::ConfigDescriptorTree;19use usb_util::ControlRequestDataPhaseTransferDirection;20use usb_util::ControlRequestRecipient;21use usb_util::ControlRequestType;22use usb_util::DescriptorType;23use usb_util::DeviceDescriptorTree;24use usb_util::DeviceSpeed;25use usb_util::EndpointDirection;26use usb_util::EndpointType;27use usb_util::Error as UsbUtilError;28use usb_util::TransferBuffer;29use usb_util::TransferStatus;30use usb_util::UsbRequestSetup;31use zerocopy::FromBytes;32use zerocopy::IntoBytes;3334use crate::usb::backend::device::BackendDevice;35use crate::usb::backend::device::DeviceState;36use crate::usb::backend::endpoint::ControlEndpointState;37use crate::usb::backend::endpoint::UsbEndpoint;38use crate::usb::backend::error::Error as BackendError;39use crate::usb::backend::error::Result as BackendResult;40use crate::usb::backend::fido_backend::constants;41use crate::usb::backend::fido_backend::error::Error;42use crate::usb::backend::fido_backend::error::Result;43use crate::usb::backend::fido_backend::fido_device::FidoDevice;44use crate::usb::backend::fido_backend::poll_thread::poll_for_pending_packets;45use crate::usb::backend::fido_backend::transfer::FidoTransfer;46use crate::usb::backend::fido_backend::transfer::FidoTransferHandle;47use crate::usb::backend::transfer::BackendTransferHandle;48use crate::usb::backend::transfer::BackendTransferType;49use crate::usb::backend::transfer::ControlTransferState;50use crate::usb::backend::transfer::GenericTransferHandle;51use crate::usb::xhci::xhci_backend_device::BackendType;52use crate::usb::xhci::xhci_backend_device::UsbDeviceAddress;53use crate::usb::xhci::xhci_backend_device::XhciBackendDevice;54use crate::utils::AsyncJobQueue;55use crate::utils::EventLoop;5657/// Host-level fido passthrough device that handles USB operations and relays them to the58/// appropriate virtual fido device.59pub struct FidoPassthroughDevice {60/// The virtual FIDO device implementation.61device: Arc<Mutex<FidoDevice>>,62/// The state of the device as seen by the backend provider.63state: Arc<RwLock<DeviceState>>,64/// The state of the control transfer exchange with the xhci layer.65control_transfer_state: Arc<RwLock<ControlTransferState>>,66transfer_job_queue: Arc<AsyncJobQueue>,67kill_evt: Event,68worker_thread: Option<WorkerThread<()>>,69pending_in_transfers:70Arc<Mutex<VecDeque<(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>)>>>,71}7273impl FidoPassthroughDevice {74pub fn new(75device: Arc<Mutex<FidoDevice>>,76state: DeviceState,77event_loop: Arc<EventLoop>,78) -> Result<Self> {79let control_transfer_state = ControlTransferState {80ctl_ep_state: ControlEndpointState::SetupStage,81control_request_setup: UsbRequestSetup::new(0, 0, 0, 0, 0),82executed: false,83};84let job_queue = AsyncJobQueue::init(&event_loop).map_err(Error::StartAsyncFidoQueue)?;85Ok(FidoPassthroughDevice {86device,87state: Arc::new(RwLock::new(state)),88control_transfer_state: Arc::new(RwLock::new(control_transfer_state)),89transfer_job_queue: job_queue,90kill_evt: Event::new().unwrap(),91worker_thread: None,92pending_in_transfers: Arc::new(Mutex::new(VecDeque::new())),93})94}9596/// This function is called from the low-level event handler when the monitored `fd` is ready97/// to transmit data from the host to the guest.98pub fn read_hidraw_file(&mut self) -> Result<()> {99let mut device = self.device.lock();100// Device has already stopped working, just return early.101if device.is_device_lost {102return Ok(());103}104if !device.is_active {105// We should NEVER be polling on the fd and wake up if no transactions have been106// initiated from the guest first.107error!("Fido device received fd poll event from inactive device. This is a bug.");108return Err(Error::InconsistentFidoDeviceState);109}110111let mut packet = vec![0; constants::U2FHID_PACKET_SIZE * 2];112113if device.guest_key.lock().pending_in_packets.len() >= constants::U2FHID_MAX_IN_PENDING {114return Err(Error::PendingInQueueFull);115}116117let read_result = device.fd.lock().read(&mut packet);118match read_result {119Ok(n) => {120// We read too much, the device is misbehaving121if n != constants::U2FHID_PACKET_SIZE {122return Err(Error::ReadHidrawDevice(IOError::other(format!(123"Read too many bytes ({n}), the hidraw device is misbehaving."124))));125}126// This is safe because we just checked the size of n is exactly U2FHID_PACKET_SIZE127device128.recv_from_host(&packet[..constants::U2FHID_PACKET_SIZE].try_into().unwrap())?;129}130Err(e) => {131error!("U2F hidraw read error: {e:#}, resetting and detaching device",);132device.set_active(false);133device.is_device_lost = true;134return Err(Error::ReadHidrawDevice(e));135}136}137Ok(())138}139140/// This function is called by a queued job to handle all communication related to USB control141/// transfer packets between the guest and the virtual security key.142pub fn handle_control(143transfer: &mut FidoTransfer,144device: &Arc<Mutex<FidoDevice>>,145) -> Result<()> {146transfer.actual_length = 0;147let request_setup = match &transfer.buffer {148TransferBuffer::Vector(v) => {149UsbRequestSetup::read_from_prefix(v)150.map_err(|_| Error::InvalidDataBufferSize)?151.0152}153_ => {154return Err(Error::UnsupportedTransferBufferType);155}156};157158let mut request_setup_out = request_setup.as_bytes().to_vec();159let is_device_to_host =160request_setup.get_direction() == ControlRequestDataPhaseTransferDirection::DeviceToHost;161let descriptor_type = (request_setup.value >> 8) as u8;162163// Get Device Descriptor request164if descriptor_type == (DescriptorType::Device as u8) && is_device_to_host {165// If the descriptor is larger than the actual requested data, we only allocate space166// for the request size. This is common for USB3 control setup to request only the167// initial 8 bytes instead of the full descriptor.168let buf_size = std::cmp::min(169request_setup.length.into(),170constants::U2FHID_DEVICE_DESC.len(),171);172let mut buffer: Vec<u8> = constants::U2FHID_DEVICE_DESC[..buf_size].to_vec();173transfer.actual_length = buffer.len();174request_setup_out.append(&mut buffer);175}176177if request_setup.get_recipient() == ControlRequestRecipient::Interface {178// It's a request for the HID report descriptor179if is_device_to_host && descriptor_type == constants::HID_GET_REPORT_DESC {180let mut buffer: Vec<u8> = constants::HID_REPORT_DESC.to_vec();181transfer.actual_length = buffer.len();182request_setup_out.append(&mut buffer);183}184}185186if request_setup.get_type() == ControlRequestType::Class {187match request_setup.request {188constants::HID_GET_IDLE => {189let mut buffer: Vec<u8> = vec![0u8, 1];190buffer[0] = device.lock().guest_key.lock().idle;191transfer.actual_length = 1;192request_setup_out.append(&mut buffer);193}194constants::HID_SET_IDLE => {195device.lock().guest_key.lock().idle = (request_setup.value >> 8) as u8;196}197_ => {198debug!(199"Received unsupported setup request code of Class type: {}",200request_setup.request201);202}203}204}205206// Store the response207transfer.buffer = TransferBuffer::Vector(request_setup_out);208Ok(())209}210211/// This function is called by a queued job to handle all USB OUT requests from the guest down212/// to the host by writing the given `FidoTransfer` data into the hidraw file.213pub fn handle_interrupt_out(214transfer: &mut FidoTransfer,215device: &Arc<Mutex<FidoDevice>>,216) -> Result<()> {217let mut packet = [0u8; constants::U2FHID_PACKET_SIZE];218let buffer = match &transfer.buffer {219TransferBuffer::Vector(v) => v,220_ => {221return Err(Error::UnsupportedTransferBufferType);222}223};224if buffer.len() > constants::U2FHID_PACKET_SIZE {225error!(226"Buffer size is bigger than u2f-hid packet size: {}",227buffer.len()228);229return Err(Error::InvalidDataBufferSize);230}231packet.copy_from_slice(buffer);232let written = device.lock().recv_from_guest(&packet)?;233transfer.actual_length = written;234Ok(())235}236}237238impl Drop for FidoPassthroughDevice {239fn drop(&mut self) {240self.device.lock().is_device_lost = true;241if let Err(e) = self.kill_evt.signal() {242error!(243"Failed to send signal to stop poll worker thread, \244it might have already stopped. {e:#}"245);246}247}248}249250impl AsRawDescriptor for FidoPassthroughDevice {251fn as_raw_descriptor(&self) -> RawDescriptor {252self.device.lock().as_raw_descriptor()253}254}255256impl BackendDevice for FidoPassthroughDevice {257fn submit_backend_transfer(258&mut self,259transfer: BackendTransferType,260) -> BackendResult<BackendTransferHandle> {261let transfer = match transfer {262BackendTransferType::FidoDevice(transfer) => transfer,263_ => return Err(BackendError::MalformedBackendTransfer),264};265266let endpoint = transfer.endpoint;267let arc_transfer = Arc::new(Mutex::new(Some(transfer)));268let cancel_handle = FidoTransferHandle {269weak_transfer: Arc::downgrade(&arc_transfer),270};271272match endpoint {273constants::U2FHID_CONTROL_ENDPOINT => {274let arc_transfer_local = arc_transfer.clone();275let fido_device = self.device.clone();276self.transfer_job_queue277.queue_job(move || {278let mut lock = arc_transfer_local.lock();279match lock.take() {280Some(mut transfer) => {281if let Err(e) = FidoPassthroughDevice::handle_control(282&mut transfer,283&fido_device,284) {285error!(286"Fido device handle control failed, cancelling transfer:\287{e:#}"288);289drop(lock);290if let Err(e) = cancel_handle.cancel() {291error!(292"Failed to cancel transfer, dropping request: {e:#}"293);294return;295}296}297transfer.complete_transfer();298}299None => {300error!(301"USB transfer disappeared in handle_control. Dropping request."302);303}304}305})306.map_err(BackendError::QueueAsyncJob)?;307}308constants::U2FHID_OUT_ENDPOINT => {309let arc_transfer_local = arc_transfer.clone();310let fido_device = self.device.clone();311self.transfer_job_queue312.queue_job(move || {313let mut lock = arc_transfer_local.lock();314match lock.take() {315Some(mut transfer) => {316if let Err(e) = FidoPassthroughDevice::handle_interrupt_out(317&mut transfer,318&fido_device,319) {320error!(321"Fido device handle interrupt out failed,\322cancelling transfer: {e:#}"323);324drop(lock);325if let Err(e) = cancel_handle.cancel() {326error!(327"Failed to cancel transfer, dropping request: {e:#}"328);329return;330}331}332transfer.complete_transfer();333}334None => {335error!("Interrupt out transfer disappeared. Dropping request.");336}337}338})339.map_err(BackendError::QueueAsyncJob)?;340}341constants::U2FHID_IN_ENDPOINT => {342let handle = FidoTransferHandle {343weak_transfer: Arc::downgrade(&arc_transfer.clone()),344};345self.pending_in_transfers346.lock()347.push_back((handle, arc_transfer.clone()));348349// Make sure to arm the timer for both transfer and host packet polling as we wait350// for transaction requests to be fulfilled by the host or xhci transfer to time351// out.352if let Err(e) = self.device.lock().guest_key.lock().timer.arm() {353error!("Unable to start U2F guest key timer. U2F packets may be lost. {e:#}");354}355if let Err(e) = self.device.lock().transfer_timer.arm() {356error!("Unable to start transfer poll timer. Transfers might stall. {e:#}");357}358}359_ => {360error!("Wrong endpoint requested: {endpoint}");361return Err(BackendError::MalformedBackendTransfer);362}363}364365// Start the worker thread if it hasn't been created yet366if self.worker_thread.is_none()367&& (endpoint == constants::U2FHID_IN_ENDPOINT368|| endpoint == constants::U2FHID_OUT_ENDPOINT)369{370let device = self.device.clone();371let pending_in_transfers = self.pending_in_transfers.clone();372self.worker_thread = Some(WorkerThread::start("fido poll thread", move |kill_evt| {373if let Err(e) = poll_for_pending_packets(device, pending_in_transfers, kill_evt) {374error!("Poll worker thread errored: {e:#}");375}376}));377}378379let cancel_handle = FidoTransferHandle {380weak_transfer: Arc::downgrade(&arc_transfer),381};382Ok(BackendTransferHandle::new(cancel_handle))383}384385fn detach_event_handler(&self, _event_loop: &Arc<EventLoop>) -> BackendResult<()> {386self.device.lock().set_active(false);387Ok(())388}389390fn request_transfer_buffer(&mut self, size: usize) -> TransferBuffer {391TransferBuffer::Vector(vec![0u8; size])392}393394fn build_bulk_transfer(395&mut self,396_ep_addr: u8,397_transfer_buffer: TransferBuffer,398_stream_id: Option<u16>,399) -> BackendResult<BackendTransferType> {400// Fido devices don't support bulk transfer requests401Err(BackendError::MalformedBackendTransfer)402}403404fn build_interrupt_transfer(405&mut self,406ep_addr: u8,407transfer_buffer: TransferBuffer,408) -> BackendResult<BackendTransferType> {409Ok(BackendTransferType::FidoDevice(FidoTransfer::new(410ep_addr,411transfer_buffer,412)))413}414415fn get_control_transfer_state(&mut self) -> Arc<RwLock<ControlTransferState>> {416self.control_transfer_state.clone()417}418419fn get_device_state(&mut self) -> Arc<RwLock<DeviceState>> {420self.state.clone()421}422423fn get_active_config_descriptor(&mut self) -> BackendResult<ConfigDescriptorTree> {424// There is only a config descriptor for u2f virtual keys.425self.get_config_descriptor_by_index(0)426}427428fn get_config_descriptor(&mut self, config: u8) -> BackendResult<ConfigDescriptorTree> {429let device_descriptor = self.get_device_descriptor_tree()?;430if let Some(config_descriptor) = device_descriptor.get_config_descriptor(config) {431return Ok(config_descriptor.clone());432}433Err(BackendError::GetConfigDescriptor(434UsbUtilError::DescriptorParse,435))436}437438fn get_config_descriptor_by_index(439&mut self,440config_index: u8,441) -> BackendResult<ConfigDescriptorTree> {442let device_descriptor = self.get_device_descriptor_tree()?;443if let Some(config_descriptor) =444device_descriptor.get_config_descriptor_by_index(config_index)445{446return Ok(config_descriptor.clone());447}448Err(BackendError::GetConfigDescriptor(449UsbUtilError::DescriptorParse,450))451}452453fn get_device_descriptor_tree(&mut self) -> BackendResult<DeviceDescriptorTree> {454// Skip the first two fields of length and descriptor type as we don't need them in our455// DeviceDescriptor structure.456let mut descbuf: Vec<u8> = constants::U2FHID_DEVICE_DESC.to_vec();457let mut configbuf: Vec<u8> = constants::U2FHID_CONFIG_DESC.to_vec();458descbuf.append(&mut configbuf);459parse_usbfs_descriptors(&descbuf).map_err(BackendError::GetDeviceDescriptor)460}461462fn get_active_configuration(&mut self) -> BackendResult<u8> {463let descriptor_tree = self.get_device_descriptor_tree()?;464if descriptor_tree.bNumConfigurations != 1 {465error!(466"Fido devices should only have one configuration, found {}",467descriptor_tree.bNumConfigurations468);469} else if let Some(config_descriptor) = descriptor_tree.get_config_descriptor_by_index(0) {470return Ok(config_descriptor.bConfigurationValue);471}472Err(BackendError::GetActiveConfig(UsbUtilError::DescriptorParse))473}474475fn set_active_configuration(&mut self, config: u8) -> BackendResult<()> {476// Fido devices only have one configuration so we should do nothing here.477// Return an error if the configuration number is unexpected.478if config != 0 {479error!(480"Requested to set fido active configuration of {config}, but only 0 is allowed."481);482return Err(BackendError::BadBackendProviderState);483}484Ok(())485}486487fn clear_feature(&mut self, _value: u16, _index: u16) -> BackendResult<TransferStatus> {488// Nothing to do here, just return.489Ok(TransferStatus::Completed)490}491492fn create_endpoints(&mut self, _config_descriptor: &ConfigDescriptorTree) -> BackendResult<()> {493let mut endpoints = Vec::new();494let device_state = self.get_device_state();495// We ignore the config descriptor because u2f-hid endpoints are already defined by the496// protocol and are unchanging.497// Endpoint 1 (OUT)498endpoints.push(UsbEndpoint::new(499device_state.read().unwrap().fail_handle.clone(),500device_state.read().unwrap().job_queue.clone(),5011,502EndpointDirection::HostToDevice,503EndpointType::Interrupt,504));505// Endpoint 1 (IN)506endpoints.push(UsbEndpoint::new(507device_state.read().unwrap().fail_handle.clone(),508device_state.read().unwrap().job_queue.clone(),5091,510EndpointDirection::DeviceToHost,511EndpointType::Interrupt,512));513device_state.write().unwrap().endpoints = endpoints;514Ok(())515}516}517518impl XhciBackendDevice for FidoPassthroughDevice {519fn get_backend_type(&self) -> BackendType {520BackendType::Usb2521}522523fn get_vid(&self) -> u16 {524// Google vendor ID5250x18d1526}527528fn get_pid(&self) -> u16 {529// Unique Product ID5300xf1d0531}532533fn set_address(&mut self, _address: UsbDeviceAddress) {534// Nothing to do here535}536537fn reset(&mut self) -> BackendResult<()> {538let mut device_lock = self.device.lock();539device_lock.set_active(false);540device_lock.guest_key.lock().reset();541device_lock.transaction_manager.lock().reset();542Ok(())543}544545fn get_speed(&self) -> Option<DeviceSpeed> {546Some(DeviceSpeed::Full)547}548549fn alloc_streams(&self, _ep: u8, _num_streams: u16) -> BackendResult<()> {550// FIDO devices don't support bulk/streams so we ignore this request.551Ok(())552}553554fn free_streams(&self, _ep: u8) -> BackendResult<()> {555// FIDO devices don't support bulk/streams so we ignore this request.556Ok(())557}558559fn stop(&mut self) {560// Transition the FIDO device into inactive mode and mark device as lost.561// The FIDO device cannot error on reset so we can unwrap safely.562self.reset().unwrap();563self.device.lock().is_device_lost = true;564}565}566567568