Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 6bd9b26

Browse files
committedJan 10, 2025··
fix(sandbox): fix close after unlink
If an unlink takes place while the file is open, this would make the subsequent close fail, as soon as the file object goes out of scope and gets dropped. We introduced a HashSet that temporarily caches such unlinked file descriptors.
1 parent b1ae5cb commit 6bd9b26

File tree

2 files changed

+48
-13
lines changed

2 files changed

+48
-13
lines changed
 

‎src/hypercall.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ pub fn unlink(mem: &MmapMemory, sysunlink: &mut UnlinkParams, file_map: &mut Uhy
8787
let host_path_c_string = CString::new(host_path.as_bytes()).unwrap();
8888
sysunlink.ret = unsafe { libc::unlink(host_path_c_string.as_c_str().as_ptr()) };
8989
if sysunlink.ret >= 0 {
90-
file_map.remove_guest_path(guest_path);
90+
file_map.unlink_guest_path(guest_path);
9191
}
9292
} else {
9393
error!("The kernel requested to unlink() an unknown path ({guest_path}): Rejecting...");
@@ -144,10 +144,10 @@ pub fn open(mem: &MmapMemory, sysopen: &mut OpenParams, file_map: &mut UhyveFile
144144

145145
/// Handles an close syscall by closing the file on the host.
146146
pub fn close(sysclose: &mut CloseParams, file_map: &mut UhyveFileMap) {
147-
if file_map.get_fd_presence(sysclose.fd.into_raw_fd()) {
147+
if file_map.is_fd_closable(sysclose.fd.into_raw_fd()) {
148148
if sysclose.ret > 2 {
149149
unsafe { sysclose.ret = libc::close(sysclose.fd) }
150-
file_map.remove_fd(sysclose.fd);
150+
file_map.close_fd(sysclose.fd);
151151
} else {
152152
// Ignore closes of stdin, stdout and stderr that would
153153
// otherwise affect Uhyve
@@ -160,7 +160,7 @@ pub fn close(sysclose: &mut CloseParams, file_map: &mut UhyveFileMap) {
160160

161161
/// Handles an read syscall on the host.
162162
pub fn read(mem: &MmapMemory, sysread: &mut ReadParams, file_map: &mut UhyveFileMap) {
163-
if file_map.get_fd_presence(sysread.fd.into_raw_fd()) {
163+
if file_map.is_fd_present(sysread.fd.into_raw_fd()) {
164164
unsafe {
165165
let bytes_read = libc::read(
166166
sysread.fd,
@@ -207,7 +207,7 @@ pub fn write<B: VirtualizationBackend>(
207207
})?
208208
};
209209
return parent_vm.serial_output(bytes);
210-
} else if !file_map.get_fd_presence(syswrite.fd.into_raw_fd()) {
210+
} else if !file_map.is_fd_present(syswrite.fd.into_raw_fd()) {
211211
// We don't write anything if the file descriptor is not available,
212212
// but this is OK for now, as we have no means of returning an error codee
213213
// and writes are not necessarily guaranteed to write anything.
@@ -243,7 +243,7 @@ pub fn write<B: VirtualizationBackend>(
243243

244244
/// Handles an write syscall on the host.
245245
pub fn lseek(syslseek: &mut LseekParams, file_map: &mut UhyveFileMap) {
246-
if file_map.get_fd_presence(syslseek.fd.into_raw_fd()) {
246+
if file_map.is_fd_present(syslseek.fd.into_raw_fd()) {
247247
unsafe {
248248
syslseek.offset =
249249
libc::lseek(syslseek.fd, syslseek.offset as i64, syslseek.whence) as isize;

‎src/isolation/filemap.rs

+42-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::{
2-
collections::HashMap,
2+
collections::{HashMap, HashSet},
33
ffi::{CString, OsString},
44
fs::canonicalize,
55
io::ErrorKind,
@@ -20,6 +20,7 @@ pub struct UhyveFileMap {
2020
files: HashMap<String, OsString>,
2121
tempdir: TempDir,
2222
fdmap: BiHashMap<RawFd, String>,
23+
unlinkedfd: HashSet<RawFd>,
2324
}
2425

2526
impl UhyveFileMap {
@@ -36,6 +37,7 @@ impl UhyveFileMap {
3637
.collect(),
3738
tempdir: create_temp_dir(),
3839
fdmap: BiHashMap::new(),
40+
unlinkedfd: HashSet::new(),
3941
}
4042
}
4143

@@ -123,8 +125,20 @@ impl UhyveFileMap {
123125
ret
124126
}
125127

126-
pub fn get_fd_presence(&mut self, fd: RawFd) -> bool {
127-
debug!("get_fd_presence: {:#?}", &self.fdmap);
128+
/// Checks whether the fd is mapped to a guest path or belongs
129+
/// to an unlinked file.
130+
///
131+
/// * `fd` - The opened guest path's file descriptor.
132+
pub fn is_fd_closable(&mut self, fd: RawFd) -> bool {
133+
debug!("is_fd_closable: {:#?}", &self.fdmap);
134+
self.is_fd_present(fd) || self.unlinkedfd.contains(&fd)
135+
}
136+
137+
/// Checks whether the fd is mapped to a guest path.
138+
///
139+
/// * `fd` - The opened guest path's file descriptor.
140+
pub fn is_fd_present(&mut self, fd: RawFd) -> bool {
141+
debug!("is_fd_present: {:#?}", &self.fdmap);
128142
if (fd >= 0 && self.fdmap.contains_left(&fd)) || (0..=2).contains(&fd) {
129143
return true;
130144
}
@@ -136,10 +150,28 @@ impl UhyveFileMap {
136150
/// * `fd` - The opened guest path's file descriptor.
137151
/// * `guest_path` - The guest path.
138152
pub fn insert_fd_path(&mut self, fd: RawFd, guest_path: &str) {
139-
debug!("insert_fd_path: {:#?}", &self.fdmap);
140153
if fd > 2 {
141154
self.fdmap.insert(fd, guest_path.into());
142155
}
156+
debug!("insert_fd_path: {:#?}", &self.fdmap);
157+
}
158+
159+
/// Removes an fd from UhyveFileMap. This is only used by [crate::hypercall::close],
160+
/// under the expectation that a new temporary file will be created if the guest
161+
/// attempts to open a file of the same path again.
162+
///
163+
/// * `fd` - The file descriptor of the file being removed.
164+
pub fn close_fd(&mut self, fd: RawFd) {
165+
debug!("close_fd: {:#?}", &self.fdmap);
166+
// The file descriptor in fdclosed is supposedly still alive.
167+
// It is safe to assume that the host OS will not assign the
168+
// same file descriptor to another opened file, until _after_
169+
// the file has been closed.
170+
if let Some(&fd) = self.unlinkedfd.get(&fd) {
171+
self.unlinkedfd.remove(&fd);
172+
} else {
173+
self.remove_fd(fd);
174+
}
143175
}
144176

145177
/// Removes an fd from UhyveFileMap. This is only used by [crate::hypercall::close],
@@ -159,10 +191,13 @@ impl UhyveFileMap {
159191
/// attempts to open a file of the same path again.
160192
///
161193
/// * `guest_path` - The path of the file being removed.
162-
pub fn remove_guest_path(&mut self, guest_path: &str) {
163-
debug!("remove_guest_path: {:#?}", guest_path);
194+
pub fn unlink_guest_path(&mut self, guest_path: &str) {
195+
debug!("unlink_guest_path: {:#?}", &guest_path);
196+
if let Some(fd) = self.fdmap.get_by_right(guest_path) {
197+
self.unlinkedfd.insert(*fd);
198+
self.fdmap.remove_by_right(guest_path);
199+
}
164200
self.files.remove(guest_path);
165-
self.fdmap.remove_by_right(guest_path);
166201
}
167202
}
168203

0 commit comments

Comments
 (0)
Please sign in to comment.