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 5 use std::fs::File; 6 use std::io::Error as IOError; 7 use std::io::ErrorKind; 8 use std::io::Write; 9 use std::sync::Arc; 10 11 use base::debug; 12 use base::error; 13 use base::warn; 14 use base::AsRawDescriptor; 15 use base::EventType; 16 use base::RawDescriptor; 17 use sync::Mutex; 18 use zerocopy::FromBytes; 19 20 use crate::usb::backend::fido_backend::constants; 21 use crate::usb::backend::fido_backend::error::Error; 22 use crate::usb::backend::fido_backend::error::Result; 23 use crate::usb::backend::fido_backend::fido_guest::FidoGuestKey; 24 use crate::usb::backend::fido_backend::fido_transaction::TransactionManager; 25 use crate::usb::backend::fido_backend::hid_utils::verify_is_fido_device; 26 use crate::usb::backend::fido_backend::poll_thread::PollTimer; 27 use crate::utils::EventLoop; 28 29 #[derive(FromBytes, Debug)] 30 #[repr(C)] 31 pub struct InitPacket { 32 cid: [u8; constants::CID_SIZE], 33 cmd: u8, 34 bcnth: u8, 35 bcntl: u8, 36 data: [u8; constants::PACKET_INIT_DATA_SIZE], 37 } 38 39 impl InitPacket { extract_cid(bytes: &[u8; constants::U2FHID_PACKET_SIZE]) -> [u8; constants::CID_SIZE]40 pub fn extract_cid(bytes: &[u8; constants::U2FHID_PACKET_SIZE]) -> [u8; constants::CID_SIZE] { 41 // cid is the first 4 bytes. `U2FHID_PACKET_SIZE` > 4, so this cannot fail. 42 bytes[0..constants::CID_SIZE].try_into().unwrap() 43 } 44 is_valid(bytes: &[u8; constants::U2FHID_PACKET_SIZE]) -> bool45 fn is_valid(bytes: &[u8; constants::U2FHID_PACKET_SIZE]) -> bool { 46 (bytes[4] & constants::PACKET_INIT_VALID_CMD) != 0 47 } 48 from_bytes(bytes: &[u8; constants::U2FHID_PACKET_SIZE]) -> Result<InitPacket>49 pub fn from_bytes(bytes: &[u8; constants::U2FHID_PACKET_SIZE]) -> Result<InitPacket> { 50 if !InitPacket::is_valid(bytes) { 51 return Err(Error::InvalidInitPacket); 52 } 53 54 InitPacket::read_from_bytes(bytes).map_err(|_| Error::CannotConvertInitPacketFromBytes) 55 } 56 bcnt(&self) -> u1657 pub fn bcnt(&self) -> u16 { 58 (self.bcnth as u16) << 8 | (self.bcntl as u16) 59 } 60 } 61 62 /// A virtual representation of a FidoDevice emulated on the Host. 63 pub struct FidoDevice { 64 /// Guest representation of the virtual security key device 65 pub guest_key: Arc<Mutex<FidoGuestKey>>, 66 /// The `TransactionManager` which handles starting and stopping u2f transactions 67 pub transaction_manager: Arc<Mutex<TransactionManager>>, 68 /// Marks whether the current device is active in a transaction. If it is not active, the fd 69 /// polling event loop does not handle the device fd monitoring. 70 pub is_active: bool, 71 /// Marks whether the device has been lost. In case the FD stops being responsive we signal 72 /// that the device is lost and any further transaction will return a failure. 73 pub is_device_lost: bool, 74 /// Backend provider event loop to attach/detach the monitored fd. 75 event_loop: Arc<EventLoop>, 76 /// Timer to poll for active USB transfers 77 pub transfer_timer: PollTimer, 78 /// fd of the actual hidraw device 79 pub fd: Arc<Mutex<File>>, 80 } 81 82 impl AsRawDescriptor for FidoDevice { as_raw_descriptor(&self) -> RawDescriptor83 fn as_raw_descriptor(&self) -> RawDescriptor { 84 self.fd.lock().as_raw_descriptor() 85 } 86 } 87 88 impl FidoDevice { new(hidraw: File, event_loop: Arc<EventLoop>) -> Result<FidoDevice>89 pub fn new(hidraw: File, event_loop: Arc<EventLoop>) -> Result<FidoDevice> { 90 verify_is_fido_device(&hidraw)?; 91 let timer = PollTimer::new( 92 "USB transfer timer".to_string(), 93 std::time::Duration::from_millis(constants::USB_POLL_RATE_MILLIS), 94 )?; 95 Ok(FidoDevice { 96 guest_key: Arc::new(Mutex::new(FidoGuestKey::new()?)), 97 transaction_manager: Arc::new(Mutex::new(TransactionManager::new()?)), 98 is_active: false, 99 is_device_lost: false, 100 event_loop, 101 transfer_timer: timer, 102 fd: Arc::new(Mutex::new(hidraw)), 103 }) 104 } 105 106 /// Sets the device active state. If the device becomes active, it toggles polling on the file 107 /// descriptor for the host hid device. If the devices becomes inactive, it stops polling. 108 /// In case of error, it's not possible to recover so we just log the warning and continue. set_active(&mut self, active: bool)109 pub fn set_active(&mut self, active: bool) { 110 if self.is_active && !active { 111 if let Err(e) = self.event_loop.pause_event_for_descriptor(self) { 112 error!("Could not deactivate polling of host device: {}", e); 113 } 114 } else if !self.is_active && active { 115 if let Err(e) = self 116 .event_loop 117 .resume_event_for_descriptor(self, EventType::Read) 118 { 119 error!( 120 "Could not resume polling of host device, transactions will be lost: {}", 121 e 122 ); 123 } 124 } 125 126 self.is_active = active; 127 } 128 129 /// Starts a new transaction from a given init packet. start_transaction(&mut self, packet: &InitPacket) -> Result<()>130 pub fn start_transaction(&mut self, packet: &InitPacket) -> Result<()> { 131 let nonce = if packet.cid == constants::BROADCAST_CID { 132 packet.data[..constants::NONCE_SIZE] 133 .try_into() 134 .map_err(|_| Error::InvalidNonceSize)? 135 } else { 136 constants::EMPTY_NONCE 137 }; 138 139 // Start a transaction and the expiration timer if necessary 140 if self 141 .transaction_manager 142 .lock() 143 .start_transaction(packet.cid, nonce) 144 { 145 // Enable the timer that polls for transactions to expire 146 self.transaction_manager.lock().transaction_timer.arm()?; 147 } 148 149 // Transition the low level device to active for a response from the host 150 self.set_active(true); 151 Ok(()) 152 } 153 154 /// Receives a low-level request from the host device. It means we read data from the actual 155 /// key on the host. recv_from_host(&mut self, packet: &[u8; constants::U2FHID_PACKET_SIZE]) -> Result<()>156 pub fn recv_from_host(&mut self, packet: &[u8; constants::U2FHID_PACKET_SIZE]) -> Result<()> { 157 let cid = InitPacket::extract_cid(packet); 158 let transaction_opt = if cid == constants::BROADCAST_CID { 159 match InitPacket::from_bytes(packet) { 160 Ok(packet) => { 161 // This is a special case, in case of an error message we return to the 162 // latest broadcast transaction without nonce checking. 163 if packet.cmd == constants::U2FHID_ERROR_CMD { 164 self.transaction_manager.lock().get_transaction(cid) 165 // Otherwise we verify that the nonce matches the right transaction. 166 } else { 167 let nonce = packet.data[..constants::NONCE_SIZE] 168 .try_into() 169 .map_err(|_| Error::InvalidNonceSize)?; 170 self.transaction_manager 171 .lock() 172 .get_transaction_from_nonce(nonce) 173 } 174 } 175 _ => { 176 // Drop init transaction with bad init packet 177 return Ok(()); 178 } 179 } 180 } else { 181 self.transaction_manager.lock().get_transaction(cid) 182 }; 183 184 let transaction = match transaction_opt { 185 Some(t) => t, 186 None => { 187 debug!("Ignoring non-started transaction"); 188 return Ok(()); 189 } 190 }; 191 192 match InitPacket::from_bytes(packet) { 193 Ok(packet) => { 194 if packet.cid == constants::BROADCAST_CID { 195 let nonce = &packet.data[..constants::NONCE_SIZE]; 196 if transaction.nonce != nonce { 197 // In case of an error command we can let it through, otherwise we drop the 198 // response. 199 if packet.cmd != constants::U2FHID_ERROR_CMD { 200 warn!( 201 "u2f: received a broadcast transaction with mismatched nonce.\ 202 Ignoring transaction." 203 ); 204 return Ok(()); 205 } 206 } 207 } 208 self.transaction_manager.lock().update_transaction( 209 cid, 210 packet.bcnt(), 211 constants::PACKET_INIT_DATA_SIZE as u16, 212 ); 213 } 214 // It's not an init packet, it means it's a continuation packet 215 Err(Error::InvalidInitPacket) => { 216 self.transaction_manager.lock().update_transaction( 217 cid, 218 transaction.resp_bcnt, 219 transaction.resp_size + constants::PACKET_CONT_DATA_SIZE as u16, 220 ); 221 } 222 Err(e) => { 223 error!( 224 "u2f: received an invalid transaction state: {:?}. Ignoring transaction.", 225 e 226 ); 227 return Ok(()); 228 } 229 } 230 231 // Fetch the transaction again to check if we are done processing it or if we should wait 232 // for more continuation packets. 233 let transaction = match self.transaction_manager.lock().get_transaction(cid) { 234 Some(t) => t, 235 None => { 236 error!( 237 "We lost a transaction on the way. This is a bug. (cid: {:?})", 238 cid 239 ); 240 return Ok(()); 241 } 242 }; 243 // Check for the end of the transaction 244 if transaction.resp_size >= transaction.resp_bcnt { 245 if self 246 .transaction_manager 247 .lock() 248 .close_transaction(transaction.cid) 249 { 250 // Resets the device as inactive, since we're not waiting for more data to come 251 // from the host. 252 self.set_active(false); 253 } 254 } 255 256 let mut guest_key = self.guest_key.lock(); 257 if guest_key.pending_in_packets.is_empty() { 258 // We start polling waiting to send the data back to the guest. 259 if let Err(e) = guest_key.timer.arm() { 260 error!( 261 "Unable to start U2F guest key timer. U2F packets may be lost. {}", 262 e 263 ); 264 } 265 } 266 guest_key.pending_in_packets.push_back(*packet); 267 268 Ok(()) 269 } 270 271 /// Receives a request from the guest device to write into the actual device on the host. recv_from_guest( &mut self, packet: &[u8; constants::U2FHID_PACKET_SIZE], ) -> Result<usize>272 pub fn recv_from_guest( 273 &mut self, 274 packet: &[u8; constants::U2FHID_PACKET_SIZE], 275 ) -> Result<usize> { 276 // The first byte in the host packet request is the HID report request ID as required by 277 // the Linux kernel. The real request data starts from the second byte, so we need to 278 // allocate one extra byte in our write buffer. 279 // See: https://docs.kernel.org/hid/hidraw.html#write 280 let mut host_packet = vec![0; constants::U2FHID_PACKET_SIZE + 1]; 281 282 match InitPacket::from_bytes(packet) { 283 Ok(init_packet) => { 284 self.start_transaction(&init_packet)?; 285 } 286 Err(Error::InvalidInitPacket) => { 287 // It's not an init packet, so we don't start a transaction. 288 } 289 Err(e) => { 290 warn!("Received malformed or invalid u2f-hid init packet, request will be dropped"); 291 return Err(e); 292 } 293 } 294 295 host_packet[1..].copy_from_slice(packet.as_slice()); 296 297 let written = self 298 .fd 299 .lock() 300 .write(&host_packet) 301 .map_err(Error::WriteHidrawDevice)?; 302 303 if written != host_packet.len() { 304 return Err(Error::WriteHidrawDevice(IOError::new( 305 ErrorKind::Other, 306 "Wrote too few bytes to hidraw device.", 307 ))); 308 } 309 310 // we subtract 1 because we added 1 extra byte to the host packet 311 Ok(host_packet.len() - 1) 312 } 313 } 314