-
Notifications
You must be signed in to change notification settings - Fork 29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create sandbox for host file system access #783
base: main
Are you sure you want to change the base?
Changes from 3 commits
b03b71c
6917513
0b243c1
2246517
b327406
f50841f
ca0e3c4
2f5355f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
use std::{ | ||
ffi::OsStr, | ||
ffi::{CStr, CString, OsStr}, | ||
io::{self, Error, ErrorKind, Write}, | ||
os::unix::ffi::OsStrExt, | ||
}; | ||
|
@@ -8,6 +8,7 @@ use uhyve_interface::{parameters::*, GuestPhysAddr, Hypercall, HypercallAddress, | |
|
||
use crate::{ | ||
consts::BOOT_PML4, | ||
isolation::UhyveFileMap, | ||
mem::{MemoryError, MmapMemory}, | ||
virt_to_phys, | ||
}; | ||
|
@@ -84,13 +85,46 @@ pub fn unlink(mem: &MmapMemory, sysunlink: &mut UnlinkParams) { | |
} | ||
|
||
/// Handles an open syscall by opening a file on the host. | ||
pub fn open(mem: &MmapMemory, sysopen: &mut OpenParams) { | ||
unsafe { | ||
sysopen.ret = libc::open( | ||
mem.host_address(sysopen.name).unwrap() as *const i8, | ||
sysopen.flags, | ||
sysopen.mode, | ||
); | ||
pub fn open(mem: &MmapMemory, sysopen: &mut OpenParams, file_map: &Option<UhyveFileMap>) { | ||
// TODO: We could keep track of the file descriptors internally, in case the kernel doesn't close them. | ||
let requested_path = mem.host_address(sysopen.name).unwrap() as *const i8; | ||
|
||
// If the file_map doesn't exist, full host filesystem access will be provided. | ||
if let Some(file_map) = file_map { | ||
// Rust deals in UTF-8. C doesn't provide such a guarantee. | ||
// In that case, converting a CStr to str will return a Utf8Error. | ||
// | ||
// See: https://nrc.github.io/big-book-ffi/reference/strings.html | ||
let guest_path = unsafe { CStr::from_ptr(requested_path) }.to_str(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd much prefer a checked safe variant here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see any better way of doing this in https://doc.rust-lang.org/std/ffi/struct.CStr.html I presumed |
||
|
||
if let Ok(guest_path) = guest_path { | ||
let host_path_option = file_map.get_host_path(guest_path); | ||
if let Some(host_path) = host_path_option { | ||
// This variable has to exist, as pointers don't have a lifetime | ||
// and appending .as_ptr() would lead to the string getting | ||
// immediately deallocated after the statement. Nothing is | ||
// referencing it as far as the type system is concerned". | ||
// | ||
// This is also why we can't just have one unsafe block and | ||
// one path variable, otherwise we'll get a use after free. | ||
let host_path_c_string = CString::new(host_path.as_bytes()).unwrap(); | ||
let new_host_path = host_path_c_string.as_c_str().as_ptr(); | ||
|
||
unsafe { | ||
sysopen.ret = libc::open(new_host_path, sysopen.flags, sysopen.mode); | ||
} | ||
} else { | ||
error!("The kernel requested to open() a non-whitelisted path. Rejecting..."); | ||
sysopen.ret = -1; | ||
} | ||
} else { | ||
error!("The kernel requested to open() a path that is not valid UTF-8. Rejecting..."); | ||
sysopen.ret = -1; | ||
} | ||
} else { | ||
unsafe { | ||
sysopen.ret = libc::open(requested_path, sysopen.flags, sysopen.mode); | ||
} | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
use std::{collections::HashMap, ffi::OsString, fs, path::PathBuf}; | ||
|
||
/// HashMap matching a path in the guest OS ([String]) a path in the host OS ([OsString]). | ||
/// | ||
/// Using a list of parameters stored in a [Vec<String>], this function creates | ||
/// a HashMap that can match a path on the host operating system given a path on | ||
/// the guest operating system. | ||
/// | ||
/// See [crate::hypercall::open] to see this in practice. | ||
pub struct UhyveFileMap { | ||
files: HashMap<String, OsString>, | ||
} | ||
|
||
impl UhyveFileMap { | ||
/// Creates a UhyveFileMap. | ||
/// | ||
/// * `parameters` - A list of parameters with the format `./host_path.txt:guest.txt` | ||
pub fn new(parameters: &[String]) -> Option<UhyveFileMap> { | ||
Some(UhyveFileMap { | ||
files: parameters | ||
.iter() | ||
.map(String::as_str) | ||
.map(Self::split_guest_and_host_path) | ||
.map(|(guest_path, host_path)| { | ||
( | ||
guest_path, | ||
fs::canonicalize(&host_path).map_or(host_path, PathBuf::into_os_string), | ||
) | ||
}) | ||
.collect(), | ||
}) | ||
} | ||
|
||
/// Separates a string of the format "./host_dir/host_path.txt:guest_path.txt" | ||
/// into a guest_path (String) and host_path (OsString) respectively. | ||
/// | ||
/// `parameter` - A parameter of the format `./host_path.txt:guest.txt`. | ||
fn split_guest_and_host_path(parameter: &str) -> (String, OsString) { | ||
let mut partsiter = parameter.split(":"); | ||
|
||
// Mind the order. | ||
// TODO: Do this work using clap. | ||
let host_path = OsString::from(partsiter.next().unwrap()); | ||
let guest_path = partsiter.next().unwrap().to_owned(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is "by design" for the time being, but suboptimal - there is definitely room for improvement here. Given that I'm targeting Something that @cagatay-y brought up to me is that using a bunch of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More specifically, I think it is possible that if the function signature is changed to return an Option in the future, it is possible that a None caused by an error in parsing may get confused with the None variant that signifies unsandboxed filesystem access. |
||
|
||
(guest_path, host_path) | ||
} | ||
|
||
/// Returns the host_path on the host filesystem given a requested guest_path, if it exists. | ||
/// | ||
/// This function will look up the requested file in the UhyveFileMap and return | ||
/// the corresponding path. | ||
/// | ||
/// `guest_path` - The guest path. The file that the kernel is trying to open. | ||
pub fn get_host_path(&self, guest_path: &str) -> Option<&OsString> { | ||
self.files.get(guest_path) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_split_guest_and_host_path() { | ||
let host_guest_strings = vec![ | ||
"./host_string.txt:guest_string.txt", | ||
"/home/user/host_string.txt:guest_string.md.txt", | ||
":guest_string.conf", | ||
":", | ||
"exists.txt:also_exists.txt:should_not_exist.txt", | ||
]; | ||
|
||
// Mind the inverted order. | ||
let results = vec![ | ||
( | ||
String::from("guest_string.txt"), | ||
OsString::from("./host_string.txt"), | ||
), | ||
( | ||
String::from("guest_string.md.txt"), | ||
OsString::from("/home/user/host_string.txt"), | ||
), | ||
(String::from("guest_string.conf"), OsString::from("")), | ||
(String::from(""), OsString::from("")), | ||
( | ||
String::from("also_exists.txt"), | ||
OsString::from("exists.txt"), | ||
), | ||
]; | ||
|
||
for (i, host_and_guest_string) in host_guest_strings | ||
.into_iter() | ||
.map(UhyveFileMap::split_guest_and_host_path) | ||
.enumerate() | ||
{ | ||
assert_eq!(host_and_guest_string.0, results[i].0); | ||
assert_eq!(host_and_guest_string.1, results[i].1); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be a better interface, if we have the separate mounts as separate options. Like
uhvye -v a.txt:b.txt -v c.txt:/bla/blub.txt
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The thing with the separate options works by default. I removed the delimiter and the environment variable for now, and changed
file_map
intomount
(while still referring to afile_map
internally, leaving room for further extensions in the future).As described in private (after a discussion with @mkroening), the middle ground that I found for this was to call the parameter
--mount
again without introducing ashort
.-v
is already taken by--verbose
(just like-m
for--file-map
, only-f
isn't taken but that's a weird choice).