-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
Copy pathdevices.rs
320 lines (266 loc) · 11.9 KB
/
devices.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#![allow(clippy::undocumented_unsafe_blocks)]
use std::os::raw::{c_int, c_void};
use std::os::unix::io::{AsRawFd, RawFd};
use std::sync::{Arc, Mutex};
use event_manager::{EventManager, SubscriberOps};
use libc::EFD_NONBLOCK;
use vm_superio::Serial;
use vmm::devices::legacy::serial::SerialOut;
use vmm::devices::legacy::{EventFdTrigger, SerialEventsWrapper, SerialWrapper};
use vmm_sys_util::eventfd::EventFd;
fn create_serial(
pipe: c_int,
) -> Arc<Mutex<SerialWrapper<EventFdTrigger, SerialEventsWrapper, Box<MockSerialInput>>>> {
// Serial input is the reading end of the pipe.
let serial_in = MockSerialInput(pipe);
let kick_stdin_evt = EventFdTrigger::new(EventFd::new(libc::EFD_NONBLOCK).unwrap());
Arc::new(Mutex::new(SerialWrapper {
serial: Serial::with_events(
EventFdTrigger::new(EventFd::new(EFD_NONBLOCK).unwrap()),
SerialEventsWrapper {
buffer_ready_event_fd: Some(kick_stdin_evt.try_clone().unwrap()),
},
SerialOut::Stdout(std::io::stdout()),
),
input: Some(Box::new(serial_in)),
}))
}
#[derive(Debug)]
pub struct MockSerialInput(pub RawFd);
impl std::io::Read for MockSerialInput {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let count = unsafe { libc::read(self.0, buf.as_mut_ptr().cast(), buf.len()) };
usize::try_from(count).map_err(|_| std::io::Error::last_os_error())
}
}
impl AsRawFd for MockSerialInput {
fn as_raw_fd(&self) -> RawFd {
self.0
}
}
#[test]
fn test_issue_serial_hangup_anon_pipe_while_registered_stdin() {
let mut fds: [c_int; 2] = [0; 2];
let rc = unsafe { libc::pipe(fds.as_mut_ptr()) };
assert!(rc == 0);
// Serial input is the reading end of the pipe.
let serial = create_serial(fds[0]);
// Make reading fd non blocking to read just what is inflight.
let flags = unsafe { libc::fcntl(fds[0], libc::F_GETFL, 0) };
let mut rc = unsafe { libc::fcntl(fds[0], libc::F_SETFL, flags | libc::O_NONBLOCK) };
assert!(rc == 0);
const BYTES_COUNT: usize = 65; // Serial FIFO_SIZE + 1.
let mut dummy_data = [1u8; BYTES_COUNT];
rc = unsafe {
libc::write(
fds[1],
dummy_data.as_mut_ptr() as *const c_void,
dummy_data.len(),
) as i32
};
assert!(dummy_data.len() == usize::try_from(rc).unwrap());
// Register the reading end of the pipe to the event manager, to be processed later on.
let mut event_manager = EventManager::new().unwrap();
let _id = event_manager.add_subscriber(serial.clone());
// `EventSet::IN` was received on stdin. The event handling will consume
// 64 bytes from stdin. The stdin monitoring is still armed.
let mut ev_count = event_manager.run().unwrap();
assert_eq!(ev_count, 1);
let mut data = [0u8; BYTES_COUNT];
// On the main thread, we will simulate guest "vCPU" thread serial reads.
let data_bus_offset = 0;
for i in 0..BYTES_COUNT - 1 {
serial
.lock()
.unwrap()
.bus_read(data_bus_offset, &mut data[i..=i]);
}
assert!(data[..31] == dummy_data[..31]);
assert!(data[32..64] == dummy_data[32..64]);
// The avail capacity of the serial FIFO is 64.
// Read the 65th from the stdin through the kick stdin event triggered by 64th of the serial
// FIFO read, or by the armed level-triggered stdin monitoring. Either one of the events might
// be handled first. The handling of the second event will find the stdin without any pending
// bytes and will result in EWOULDBLOCK. Usually, EWOULDBLOCK will reregister the stdin, but
// since it was not unregistered before, it will do a noop.
ev_count = event_manager.run().unwrap();
assert_eq!(ev_count, 2);
// The avail capacity of the serial FIFO is 63.
rc = unsafe {
libc::write(
fds[1],
dummy_data.as_mut_ptr() as *const c_void,
dummy_data.len(),
) as i32
};
assert!(dummy_data.len() == usize::try_from(rc).unwrap());
// Writing to the other end of the pipe triggers handling a stdin event.
// Now, 63 bytes will be read from stdin, filling up the buffer.
ev_count = event_manager.run().unwrap();
assert_eq!(ev_count, 1);
// Close the writing end (this sends an HANG_UP to the reading end).
// While the stdin is registered, this event is caught by the event manager.
rc = unsafe { libc::close(fds[1]) };
assert!(rc == 0);
// This cycle of epoll has two important events. First, the received HANGUP and second
// the fact that the FIFO is full, so even if the stdin reached EOF, there are still
// pending bytes to be read. We still unregister the stdin and keep reading from it until
// we get all pending bytes.
ev_count = event_manager.run().unwrap();
assert_eq!(ev_count, 1);
// Free up 64 bytes from the serial FIFO.
for i in 0..BYTES_COUNT - 1 {
serial
.lock()
.unwrap()
.bus_read(data_bus_offset, &mut data[i..=i]);
}
// Process the kick stdin event generated by the reading of the 64th byte of the serial FIFO.
// This will consume some more bytes from the stdin while the stdin is unregistered.
ev_count = event_manager.run().unwrap();
assert_eq!(ev_count, 1);
// Two more bytes left. At the 2nd byte, another kick read stdin event is generated,
// trying to fill again the serial FIFO with more bytes.
for i in 0..2 {
serial
.lock()
.unwrap()
.bus_read(data_bus_offset, &mut data[i..=i]);
}
// We try to read again, but we detect that stdin received previously EOF.
// This can be deduced by reading from a non-blocking fd and getting 0 bytes as a result,
// instead of EWOUDBLOCK. We unregister the stdin and the kick stdin read evt.
ev_count = event_manager.run().unwrap();
assert_eq!(ev_count, 1);
// Nothing can interrupt us.
ev_count = event_manager.run_with_timeout(1).unwrap();
assert_eq!(ev_count, 0);
}
#[test]
fn test_issue_hangup() {
let mut fds: [c_int; 2] = [0; 2];
let rc = unsafe { libc::pipe(fds.as_mut_ptr()) };
assert!(rc == 0);
// Serial input is the reading end of the pipe.
let serial = create_serial(fds[0]);
// Make reading fd non blocking to read just what is inflight.
let flags = unsafe { libc::fcntl(fds[0], libc::F_GETFL, 0) };
let mut rc = unsafe { libc::fcntl(fds[0], libc::F_SETFL, flags | libc::O_NONBLOCK) };
assert!(rc == 0);
// Close the writing end (this sends an HANG_UP to the reading end).
// While the stdin is registered, this event is caught by the event manager.
rc = unsafe { libc::close(fds[1]) };
assert!(rc == 0);
// Register the reading end of the pipe to the event manager, to be processed later on.
let mut event_manager = EventManager::new().unwrap();
let _id = event_manager.add_subscriber(serial);
let mut ev_count = event_manager.run().unwrap();
assert_eq!(ev_count, 1);
// Nothing can interrupt us.
ev_count = event_manager.run_with_timeout(1).unwrap();
assert_eq!(ev_count, 0);
}
#[test]
fn test_issue_serial_hangup_anon_pipe_while_unregistered_stdin() {
let mut fds: [c_int; 2] = [0; 2];
let rc = unsafe { libc::pipe(fds.as_mut_ptr()) };
assert!(rc == 0);
// Serial input is the reading end of the pipe.
let serial = create_serial(fds[0]);
// Make reading fd non blocking to read just what is inflight.
let flags = unsafe { libc::fcntl(fds[0], libc::F_GETFL, 0) };
let mut rc = unsafe { libc::fcntl(fds[0], libc::F_SETFL, flags | libc::O_NONBLOCK) };
assert!(rc == 0);
const BYTES_COUNT: usize = 65; // Serial FIFO_SIZE + 1.
let mut dummy_data = [1u8; BYTES_COUNT];
rc = unsafe {
libc::write(
fds[1],
dummy_data.as_mut_ptr() as *const c_void,
dummy_data.len(),
) as i32
};
assert!(dummy_data.len() == usize::try_from(rc).unwrap());
// Register the reading end of the pipe to the event manager, to be processed later on.
let mut event_manager = EventManager::new().unwrap();
let _id = event_manager.add_subscriber(serial.clone());
// `EventSet::IN` was received on stdin. The event handling will consume
// 64 bytes from stdin. The stdin monitoring is still armed.
let mut ev_count = event_manager.run_with_timeout(0).unwrap();
assert_eq!(ev_count, 1);
let mut data = [0u8; BYTES_COUNT];
// On the main thread, we will simulate guest "vCPU" thread serial reads.
let data_bus_offset = 0;
for i in 0..BYTES_COUNT - 1 {
serial
.lock()
.unwrap()
.bus_read(data_bus_offset, &mut data[i..=i]);
}
assert!(data[..31] == dummy_data[..31]);
assert!(data[32..64] == dummy_data[32..64]);
// The avail capacity of the serial FIFO is 64.
// Read the 65th from the stdin through the kick stdin event triggered by 64th of the serial
// FIFO read, or by the armed level-triggered stdin monitoring. Either one of the events might
// be handled first. The handling of the second event will find the stdin without any pending
// bytes and will result in EWOULDBLOCK. Usually, EWOULDBLOCK will reregister the stdin, but
// since it was not unregistered before, it will do a noop.
ev_count = event_manager.run().unwrap();
assert_eq!(ev_count, 2);
// The avail capacity of the serial FIFO is 63.
rc = unsafe {
libc::write(
fds[1],
dummy_data.as_mut_ptr() as *const c_void,
dummy_data.len(),
) as i32
};
assert!(dummy_data.len() == usize::try_from(rc).unwrap());
// Writing to the other end of the pipe triggers handling an stdin event.
// Now, 63 bytes will be read from stdin, filling up the buffer.
ev_count = event_manager.run().unwrap();
assert_eq!(ev_count, 1);
// Serial FIFO is full, so silence the stdin. We do not need any other interruptions
// until the serial FIFO is freed.
ev_count = event_manager.run().unwrap();
assert_eq!(ev_count, 1);
// Close the writing end (this sends an HANG_UP to the reading end).
// While the stdin is unregistered, this event is not caught by the event manager.
rc = unsafe { libc::close(fds[1]) };
assert!(rc == 0);
// This would be a blocking epoll_wait, since the buffer is full and stdin is unregistered.
// There is no event that can break the epoll wait loop.
ev_count = event_manager.run_with_timeout(0).unwrap();
assert_eq!(ev_count, 0);
// Free up 64 bytes from the serial FIFO.
for i in 0..BYTES_COUNT - 1 {
serial
.lock()
.unwrap()
.bus_read(data_bus_offset, &mut data[i..=i]);
}
// Process the kick stdin event generated by the reading of the 64th byte of the serial FIFO.
// This will consume some more bytes from the stdin. Keep in mind that the HANGUP event was
// lost and we do not know that the stdin reached EOF.
ev_count = event_manager.run().unwrap();
assert_eq!(ev_count, 1);
// Two more bytes left. At the 2nd byte, another kick read stdin event is generated,
// trying to fill again the serial FIFO with more bytes. Keep in mind that the HANGUP event was
// lost and we do not know that the stdin reached EOF.
for i in 0..2 {
serial
.lock()
.unwrap()
.bus_read(data_bus_offset, &mut data[i..=i]);
}
// We try to read again, but we detect that stdin received previously EOF.
// This can be deduced by reading from a non-blocking fd and getting 0 bytes as a result,
// instead of EWOUDBLOCK. We unregister the stdin and the kick stdin read evt.
ev_count = event_manager.run().unwrap();
assert_eq!(ev_count, 1);
// Nothing can interrupt us.
ev_count = event_manager.run_with_timeout(0).unwrap();
assert_eq!(ev_count, 0);
}