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