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