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 //! This file contains all functions and structs used to handle polling operations for the fido
6 //! backend device.
7
8 use std::collections::VecDeque;
9 use std::sync::Arc;
10 use std::time::Duration;
11
12 use anyhow::Context;
13 use base::debug;
14 use base::error;
15 use base::AsRawDescriptor;
16 use base::Event;
17 use base::EventToken;
18 use base::RawDescriptor;
19 use base::Timer;
20 use base::TimerTrait;
21 use base::WaitContext;
22 use sync::Mutex;
23 use usb_util::TransferStatus;
24
25 use crate::usb::backend::fido_backend::error::Error;
26 use crate::usb::backend::fido_backend::error::Result;
27 use crate::usb::backend::fido_backend::fido_device::FidoDevice;
28 use crate::usb::backend::fido_backend::transfer::FidoTransfer;
29 use crate::usb::backend::fido_backend::transfer::FidoTransferHandle;
30 use crate::usb::backend::transfer::BackendTransfer;
31 use crate::usb::backend::transfer::GenericTransferHandle;
32
33 #[derive(EventToken)]
34 enum Token {
35 TransactionPollTimer,
36 TransferPollTimer,
37 PacketPollTimer,
38 Kill,
39 }
40
41 /// PollTimer is a wrapper around the crosvm-provided `Timer` struct with a focus on maintaining a
42 /// regular interval with easy `arm()` and `clear()` methods to start and stop the timer
43 /// transparently from the interval.
44 pub struct PollTimer {
45 name: String,
46 timer: Timer,
47 interval: Duration,
48 }
49
50 impl PollTimer {
new(name: String, interval: Duration) -> Result<Self>51 pub fn new(name: String, interval: Duration) -> Result<Self> {
52 let timer = Timer::new().map_err(Error::CannotCreatePollTimer)?;
53 Ok(PollTimer {
54 name,
55 timer,
56 interval,
57 })
58 }
59
60 /// Arms the timer with its initialized interval.
arm(&mut self) -> Result<()>61 pub fn arm(&mut self) -> Result<()> {
62 self.timer
63 .reset_oneshot(self.interval)
64 .map_err(|error| Error::CannotArmPollTimer {
65 name: self.name.clone(),
66 error,
67 })
68 }
69
70 /// Clears the timer, disarming it.
clear(&mut self) -> Result<()>71 pub fn clear(&mut self) -> Result<()> {
72 self.timer
73 .clear()
74 .map_err(|error| Error::CannotClearPollTimer {
75 name: self.name.clone(),
76 error,
77 })
78 }
79 }
80
81 impl AsRawDescriptor for PollTimer {
as_raw_descriptor(&self) -> RawDescriptor82 fn as_raw_descriptor(&self) -> RawDescriptor {
83 self.timer.as_raw_descriptor()
84 }
85 }
86
87 /// This function is the main poll thread. It periodically wakes up to emulate a USB interrupt
88 /// (poll) device behavior. It takes care of three different poll timers:
89 /// - `PacketPollTimer`: periodically polls for available USB transfers waiting for data
90 /// - `TransferPollTimer`: times out USB transfers that stay pending for too long without data
91 /// - `TransactionPollTimer`: puts the security key device to sleep when transactions time out
poll_for_pending_packets( device: Arc<Mutex<FidoDevice>>, pending_in_transfers: Arc< Mutex<VecDeque<(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>)>>, >, kill_evt: Event, ) -> Result<()>92 pub fn poll_for_pending_packets(
93 device: Arc<Mutex<FidoDevice>>,
94 pending_in_transfers: Arc<
95 Mutex<VecDeque<(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>)>>,
96 >,
97 kill_evt: Event,
98 ) -> Result<()> {
99 let device_lock = device.lock();
100 let wait_ctx: WaitContext<Token> = WaitContext::build_with(&[
101 (&device_lock.guest_key.lock().timer, Token::PacketPollTimer),
102 (&device_lock.transfer_timer, Token::TransferPollTimer),
103 (
104 &device_lock.transaction_manager.lock().transaction_timer,
105 Token::TransactionPollTimer,
106 ),
107 (&kill_evt, Token::Kill),
108 ])
109 .context("poll worker context failed")
110 .map_err(Error::WaitContextFailed)?;
111 drop(device_lock);
112
113 loop {
114 let events = wait_ctx
115 .wait()
116 .context("wait failed")
117 .map_err(Error::WaitContextFailed)?;
118 for event in events.iter().filter(|e| e.is_readable) {
119 match event.token {
120 // This timer checks that we have u2f host packets pending, waiting to be sent to
121 // the guest, and that we have a valid USB transfer from the guest waiting for
122 // data.
123 Token::PacketPollTimer => {
124 handle_packet_poll(&device, &pending_in_transfers)?;
125 // If there are still transfers waiting in the queue we continue polling.
126 if packet_timer_needs_rearm(&device, &pending_in_transfers) {
127 device.lock().guest_key.lock().timer.arm()?;
128 }
129 }
130 // This timer takes care of expiring USB transfers from the guest as they time out
131 // waiting for data from the host. It is the equivalent of a USB interrupt poll
132 // thread.
133 Token::TransferPollTimer => {
134 let mut transfers_lock = pending_in_transfers.lock();
135
136 transfers_lock.retain(process_pending_transfer);
137
138 // If the device has died, we need to tell the first pending transfer
139 // that the device has been lost at the xhci level, so we can safely detach the
140 // device from the guest.
141 if device.lock().is_device_lost {
142 let (_, transfer_opt) = match transfers_lock.pop_front() {
143 Some(tuple) => tuple,
144 None => {
145 // No pending transfers waiting for data, so we do nothing.
146 continue;
147 }
148 };
149 signal_device_lost(transfer_opt.lock().take());
150 return Ok(());
151 }
152
153 // If we still have pending transfers waiting, we keep polling, otherwise we
154 // stop.
155 if transfers_lock.len() > 0 {
156 device.lock().transfer_timer.arm()?;
157 } else {
158 device.lock().transfer_timer.clear()?;
159 }
160 }
161 // This timer takes care of timing out u2f transactions that haven't seen any
162 // activity from either guest or host for a long-enough time.
163 Token::TransactionPollTimer => {
164 // If transactions aren't expired, re-arm
165 if !device
166 .lock()
167 .transaction_manager
168 .lock()
169 .expire_transactions()
170 {
171 device
172 .lock()
173 .transaction_manager
174 .lock()
175 .transaction_timer
176 .arm()?;
177 }
178 }
179 Token::Kill => {
180 debug!("Fido poll thread exited succesfully.");
181 return Ok(());
182 }
183 }
184 }
185 }
186 }
187
188 /// Handles polling for available data to send back to the guest.
handle_packet_poll( device: &Arc<Mutex<FidoDevice>>, pending_in_transfers: &Arc< Mutex<VecDeque<(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>)>>, >, ) -> Result<()>189 fn handle_packet_poll(
190 device: &Arc<Mutex<FidoDevice>>,
191 pending_in_transfers: &Arc<
192 Mutex<VecDeque<(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>)>>,
193 >,
194 ) -> Result<()> {
195 if device.lock().is_device_lost {
196 // Rather than erroring here, we just return Ok as the case of a device being lost is
197 // handled by the transfer timer.
198 return Ok(());
199 }
200 let mut transfers_lock = pending_in_transfers.lock();
201
202 // Process and remove expired or cancelled transfers
203 transfers_lock.retain(process_pending_transfer);
204
205 if transfers_lock.is_empty() {
206 // We cannot do anything, the active transfers got pruned.
207 // Return Ok() and let the poll thread handle the missing packets.
208 return Ok(());
209 }
210
211 // Fetch first available transfer from the pending list and its fail handle.
212 let (_, transfer_opt) = match transfers_lock.pop_front() {
213 Some(tuple) => tuple,
214 None => {
215 // No pending transfers waiting for data, so we do nothing.
216 return Ok(());
217 }
218 };
219 drop(transfers_lock);
220
221 let mut transfer_lock = transfer_opt.lock();
222 let transfer = transfer_lock.take();
223
224 // Obtain the next packet from the guest key and send it to the guest
225 match device
226 .lock()
227 .guest_key
228 .lock()
229 .return_data_to_guest(transfer)?
230 {
231 None => {
232 // The transfer was successful, nothing to do.
233 Ok(())
234 }
235 transfer => {
236 // We received our transfer back, it means there's no data available to return to the
237 // guest.
238 *transfer_lock = transfer;
239 drop(transfer_lock);
240 let cancel_handle = FidoTransferHandle {
241 weak_transfer: Arc::downgrade(&transfer_opt),
242 };
243
244 // Put the transfer back into the pending queue, we can try again later.
245 pending_in_transfers
246 .lock()
247 .push_front((cancel_handle, transfer_opt));
248 Ok(())
249 }
250 }
251 }
252
253 /// Filter functions used to check for expired or canceled transfers. It is called over each
254 /// USB transfer waiting in the pending queue. Returns true if the given transfer is still valid,
255 /// otherwise false.
process_pending_transfer( transfer_handle_pair: &(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>), ) -> bool256 fn process_pending_transfer(
257 transfer_handle_pair: &(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>),
258 ) -> bool {
259 let mut lock = transfer_handle_pair.1.lock();
260 let transfer = match lock.take() {
261 Some(t) => {
262 // The transfer has already been cancelled. We report back to the xhci level and remove
263 // it.
264 if t.status() == TransferStatus::Cancelled {
265 t.complete_transfer();
266 return false;
267 }
268 // The transfer has expired, we cancel it and report back to the xhci level.
269 if t.timeout_expired() {
270 if let Err(e) = transfer_handle_pair.0.cancel() {
271 error!("Failed to properly cancel IN transfer, dropping the request: {e:#}");
272 return false;
273 }
274 t.complete_transfer();
275 return false;
276 }
277 Some(t)
278 }
279 None => {
280 // Transfer has already been removed so we can skip it.
281 return false;
282 }
283 };
284 *lock = transfer;
285
286 true
287 }
288
289 /// Signals to the current transfer that the underlying device has been lost and the xhci layer
290 /// should recover by detaching the FIDO backend.
signal_device_lost(transfer_opt: Option<FidoTransfer>)291 fn signal_device_lost(transfer_opt: Option<FidoTransfer>) {
292 if let Some(mut transfer) = transfer_opt {
293 transfer.signal_device_lost();
294 transfer.complete_transfer();
295 }
296 }
297
298 /// Checks whether we should re-arm the packet poll timer or not.
packet_timer_needs_rearm( device: &Arc<Mutex<FidoDevice>>, pending_in_transfers: &Arc< Mutex<VecDeque<(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>)>>, >, ) -> bool299 fn packet_timer_needs_rearm(
300 device: &Arc<Mutex<FidoDevice>>,
301 pending_in_transfers: &Arc<
302 Mutex<VecDeque<(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>)>>,
303 >,
304 ) -> bool {
305 let transfers_lock = pending_in_transfers.lock();
306 if transfers_lock.is_empty() {
307 // If there are no transfers pending, it means that some packet got stuck or lost,
308 // so we just reset the entire device state since no one is waiting for a
309 // response from the xhci level anyway.
310 device.lock().guest_key.lock().reset();
311 return false;
312 }
313 true
314 }
315