Skip to content

Commit 276e6e4

Browse files
committed
feat(uffd): new Runtime
The new `Runtime` struct can contain multiple uffd objects received from the UDS socket during its runtime. This is done by polling the socket and uffd file descriptors. When the socket event is polled, a new uffd object is retrieved. When the uffd fd is polled, a page fault for corresponding uffd is handled. The behaviour for page faults can be configured with a parameter passed to the `Runtime::run`. Signed-off-by: Egor Lazarchuk <yegorlz@amazon.co.uk>
1 parent 9011302 commit 276e6e4

File tree

3 files changed

+136
-81
lines changed

3 files changed

+136
-81
lines changed

src/firecracker/examples/uffd/malicious_handler.rs

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,33 @@
66
77
mod uffd_utils;
88

9-
use std::os::unix::io::AsRawFd;
9+
use std::fs::File;
10+
use std::os::unix::net::UnixListener;
1011

11-
use uffd_utils::create_pf_handler;
12+
use uffd_utils::{Runtime, UffdPfHandler};
1213

1314
fn main() {
14-
let uffd_handler = create_pf_handler();
15+
let mut args = std::env::args();
16+
let uffd_sock_path = args.nth(1).expect("No socket path given");
17+
let mem_file_path = args.next().expect("No memory file given");
1518

16-
let mut pollfd = libc::pollfd {
17-
fd: uffd_handler.uffd.as_raw_fd(),
18-
events: libc::POLLIN,
19-
revents: 0,
20-
};
19+
let file = File::open(mem_file_path).expect("Cannot open memfile");
2120

22-
// Loop, handling incoming events on the userfaultfd file descriptor.
23-
loop {
24-
let nready = unsafe { libc::poll(&mut pollfd, 1, -1) };
21+
// Get Uffd from UDS. We'll use the uffd to handle PFs for Firecracker.
22+
let listener = UnixListener::bind(uffd_sock_path).expect("Cannot bind to socket path");
23+
let (stream, _) = listener.accept().expect("Cannot listen on UDS socket");
2524

26-
if nready == -1 {
27-
panic!("Could not poll for events!")
28-
}
25+
let mut runtime = Runtime::new(stream, file);
26+
runtime.run(|uffd_handler: &mut UffdPfHandler| {
2927
// Read an event from the userfaultfd.
3028
let event = uffd_handler
3129
.uffd
3230
.read_event()
3331
.expect("Failed to read uffd_msg")
3432
.expect("uffd_msg not ready");
3533

36-
// We expect to receive either a Page Fault or Removed
37-
// event (if the balloon device is enabled).
3834
if let userfaultfd::Event::Pagefault { .. } = event {
3935
panic!("Fear me! I am the malicious page fault handler.")
4036
}
41-
}
37+
});
4238
}

src/firecracker/examples/uffd/uffd_utils.rs

Lines changed: 105 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use std::collections::HashMap;
88
use std::fs::File;
99
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
10-
use std::os::unix::net::{UnixListener, UnixStream};
10+
use std::os::unix::net::UnixStream;
1111
use std::ptr;
1212

1313
use serde::Deserialize;
@@ -162,6 +162,110 @@ impl UffdPfHandler {
162162
}
163163
}
164164

165+
#[derive(Debug)]
166+
pub struct Runtime {
167+
stream: UnixStream,
168+
backing_file: File,
169+
backing_memory: *mut u8,
170+
backing_memory_size: usize,
171+
uffds: HashMap<i32, UffdPfHandler>,
172+
}
173+
174+
impl Runtime {
175+
pub fn new(stream: UnixStream, backing_file: File) -> Self {
176+
let file_meta = backing_file
177+
.metadata()
178+
.expect("can not get backing file metadata");
179+
let backing_memory_size = file_meta.len() as usize;
180+
// # Safety:
181+
// File size and fd are valid
182+
let ret = unsafe {
183+
libc::mmap(
184+
ptr::null_mut(),
185+
backing_memory_size,
186+
libc::PROT_READ,
187+
libc::MAP_PRIVATE,
188+
backing_file.as_raw_fd(),
189+
0,
190+
)
191+
};
192+
if ret == libc::MAP_FAILED {
193+
panic!("mmap on backing file failed");
194+
}
195+
196+
Self {
197+
stream,
198+
backing_file,
199+
backing_memory: ret.cast(),
200+
backing_memory_size,
201+
uffds: HashMap::default(),
202+
}
203+
}
204+
205+
/// Polls the `UnixStream` and UFFD fds in a loop.
206+
/// When stream is polled, new uffd is retrieved.
207+
/// When uffd is polled, page fault is handled by
208+
/// calling `pf_event_dispatch` with corresponding
209+
/// uffd object passed in.
210+
pub fn run(&mut self, pf_event_dispatch: impl Fn(&mut UffdPfHandler)) {
211+
let mut pollfds = vec![];
212+
213+
// Poll the stream for incoming uffds
214+
pollfds.push(libc::pollfd {
215+
fd: self.stream.as_raw_fd(),
216+
events: libc::POLLIN,
217+
revents: 0,
218+
});
219+
220+
// We can skip polling on stream fd if
221+
// the connection is closed.
222+
let mut skip_stream: usize = 0;
223+
loop {
224+
let pollfd_ptr = pollfds[skip_stream..].as_mut_ptr();
225+
let pollfd_size = pollfds[skip_stream..].len() as u64;
226+
227+
// # Safety:
228+
// Pollfds vector is valid
229+
let mut nready = unsafe { libc::poll(pollfd_ptr, pollfd_size, -1) };
230+
231+
if nready == -1 {
232+
panic!("Could not poll for events!")
233+
}
234+
235+
for i in skip_stream..pollfds.len() {
236+
if nready == 0 {
237+
break;
238+
}
239+
if pollfds[i].revents & libc::POLLIN != 0 {
240+
nready -= 1;
241+
if pollfds[i].fd == self.stream.as_raw_fd() {
242+
// Handle new uffd from stream
243+
let handler = UffdPfHandler::from_unix_stream(
244+
&self.stream,
245+
self.backing_memory,
246+
self.backing_memory_size,
247+
);
248+
pollfds.push(libc::pollfd {
249+
fd: handler.uffd.as_raw_fd(),
250+
events: libc::POLLIN,
251+
revents: 0,
252+
});
253+
self.uffds.insert(handler.uffd.as_raw_fd(), handler);
254+
255+
// If connection is closed, we can skip the socket from being polled.
256+
if pollfds[i].revents & (libc::POLLRDHUP | libc::POLLHUP) != 0 {
257+
skip_stream = 1;
258+
}
259+
} else {
260+
// Handle one of uffd page faults
261+
pf_event_dispatch(self.uffds.get_mut(&pollfds[i].fd).unwrap());
262+
}
263+
}
264+
}
265+
}
266+
}
267+
}
268+
165269
fn create_mem_regions(mappings: &Vec<GuestRegionUffdMapping>) -> Vec<MemRegion> {
166270
let page_size = get_page_size().unwrap();
167271
let mut mem_regions: Vec<MemRegion> = Vec::with_capacity(mappings.len());
@@ -184,34 +288,3 @@ fn create_mem_regions(mappings: &Vec<GuestRegionUffdMapping>) -> Vec<MemRegion>
184288

185289
mem_regions
186290
}
187-
188-
pub fn create_pf_handler() -> UffdPfHandler {
189-
let uffd_sock_path = std::env::args().nth(1).expect("No socket path given");
190-
let mem_file_path = std::env::args().nth(2).expect("No memory file given");
191-
192-
let file = File::open(mem_file_path).expect("Cannot open memfile");
193-
let size = file.metadata().unwrap().len() as usize;
194-
195-
// mmap a memory area used to bring in the faulting regions.
196-
let ret = unsafe {
197-
libc::mmap(
198-
ptr::null_mut(),
199-
size,
200-
libc::PROT_READ,
201-
libc::MAP_PRIVATE,
202-
file.as_raw_fd(),
203-
0,
204-
)
205-
};
206-
if ret == libc::MAP_FAILED {
207-
panic!("mmap failed");
208-
}
209-
let memfile_buffer = ret as *const u8;
210-
211-
// Get Uffd from UDS. We'll use the uffd to handle PFs for Firecracker.
212-
let listener = UnixListener::bind(uffd_sock_path).expect("Cannot bind to socket path");
213-
214-
let (stream, _) = listener.accept().expect("Cannot listen on UDS socket");
215-
216-
UffdPfHandler::from_unix_stream(&stream, memfile_buffer, size)
217-
}

src/firecracker/examples/uffd/valid_handler.rs

Lines changed: 18 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,30 @@
77
88
mod uffd_utils;
99

10-
use std::os::unix::io::AsRawFd;
10+
use std::fs::File;
11+
use std::os::unix::net::UnixListener;
1112

12-
use uffd_utils::{create_pf_handler, MemPageState};
13+
use uffd_utils::{MemPageState, Runtime, UffdPfHandler};
1314
use utils::get_page_size;
1415

1516
fn main() {
16-
let mut uffd_handler = create_pf_handler();
17+
let mut args = std::env::args();
18+
let uffd_sock_path = args.nth(1).expect("No socket path given");
19+
let mem_file_path = args.next().expect("No memory file given");
20+
21+
let file = File::open(mem_file_path).expect("Cannot open memfile");
22+
23+
// Get Uffd from UDS. We'll use the uffd to handle PFs for Firecracker.
24+
let listener = UnixListener::bind(uffd_sock_path).expect("Cannot bind to socket path");
25+
let (stream, _) = listener.accept().expect("Cannot listen on UDS socket");
1726

1827
// Populate a single page from backing memory file.
1928
// This is just an example, probably, with the worst-case latency scenario,
2029
// of how memory can be loaded in guest RAM.
2130
let len = get_page_size().unwrap();
2231

23-
let mut pollfd = libc::pollfd {
24-
fd: uffd_handler.uffd.as_raw_fd(),
25-
events: libc::POLLIN,
26-
revents: 0,
27-
};
28-
// Loop, handling incoming events on the userfaultfd file descriptor.
29-
loop {
30-
// See what poll() tells us about the userfaultfd.
31-
let nready = unsafe { libc::poll(&mut pollfd, 1, -1) };
32-
33-
if nready == -1 {
34-
panic!("Could not poll for events!")
35-
}
36-
37-
let revents = pollfd.revents;
38-
39-
println!(
40-
"poll() returns: nready = {}; POLLIN = {}; POLLERR = {}",
41-
nready,
42-
revents & libc::POLLIN,
43-
revents & libc::POLLERR,
44-
);
45-
32+
let mut runtime = Runtime::new(stream, file);
33+
runtime.run(|uffd_handler: &mut UffdPfHandler| {
4634
// Read an event from the userfaultfd.
4735
let event = uffd_handler
4836
.uffd
@@ -53,15 +41,13 @@ fn main() {
5341
// We expect to receive either a Page Fault or Removed
5442
// event (if the balloon device is enabled).
5543
match event {
56-
userfaultfd::Event::Pagefault { addr, .. } => {
57-
uffd_handler.serve_pf(addr as *mut u8, len)
58-
}
44+
userfaultfd::Event::Pagefault { addr, .. } => uffd_handler.serve_pf(addr.cast(), len),
5945
userfaultfd::Event::Remove { start, end } => uffd_handler.update_mem_state_mappings(
60-
start as *mut u8 as u64,
61-
end as *mut u8 as u64,
46+
start as u64,
47+
end as u64,
6248
&MemPageState::Removed,
6349
),
6450
_ => panic!("Unexpected event on userfaultfd"),
6551
}
66-
}
52+
});
6753
}

0 commit comments

Comments
 (0)