Skip to content
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

Merged
merged 13 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ rftrace-frontend = { version = "0.1", optional = true }
shell-words = "1"
sysinfo = { version = "0.32.0", default-features = false, features = ["system"] }
vm-fdt = "0.3"
tempfile = "3.14.0"
uuid = { version = "1.11.0", features = ["fast-rng", "v4"]}

[target.'cfg(target_os = "linux")'.dependencies]
kvm-bindings = "0.10"
Expand Down
6 changes: 0 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,8 @@
[![crates.io](https://img.shields.io/crates/v/uhyve.svg)](https://crates.io/crates/uhyve)
[![Zulip Badge](https://img.shields.io/badge/chat-hermit-57A37C?logo=zulip)](https://hermit.zulipchat.com/)

## Introduction

Uhyve is a small hypervisor specialized for the [Hermit kernel](https://github.com/hermitcore/kernel).

> [!WARNING]
> For the time being, Uhyve provides the unikernel full host file system access with the permissions of the user running Uhyve.
> Thus, it should not be used for applications which require isolation from the host system.

## Installation

1. Install the Rust toolchain. The Rust Foundation provides [installation instructions](https://www.rust-lang.org/tools/install).
Expand Down
10 changes: 10 additions & 0 deletions src/bin/uhyve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ struct Args {
#[cfg(target_os = "linux")]
gdb_port: Option<u16>,

/// Paths that the kernel should be able to view, read or write.
///
/// Desired paths must be explicitly defined after a colon.
///
/// Example: --file-mapping host_dir:guest_dir --file-mapping file.txt:guest_file.txt
#[clap(long)]
file_mapping: Vec<String>,

/// The kernel to execute
#[clap(value_parser)]
kernel: PathBuf,
Expand Down Expand Up @@ -248,6 +256,7 @@ impl From<Args> for Params {
},
#[cfg(target_os = "linux")]
gdb_port,
file_mapping,
kernel: _,
kernel_args,
output,
Expand All @@ -262,6 +271,7 @@ impl From<Args> for Params {
cpu_count,
#[cfg(target_os = "linux")]
pit,
file_mapping,
#[cfg(target_os = "linux")]
gdb_port,
#[cfg(target_os = "macos")]
Expand Down
77 changes: 65 additions & 12 deletions src/hypercall.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use std::{
ffi::OsStr,
ffi::{CStr, CString, OsStr},
io::{self, Error, ErrorKind},
os::unix::ffi::OsStrExt,
};

use tempfile::TempDir;
use uhyve_interface::{parameters::*, GuestPhysAddr, Hypercall, HypercallAddress, MAX_ARGC_ENVC};

use crate::{
consts::BOOT_PML4,
isolation::UhyveFileMap,
mem::{MemoryError, MmapMemory},
virt_to_phys,
vm::{UhyveVm, VirtualizationBackend},
Expand Down Expand Up @@ -77,21 +79,72 @@ pub unsafe fn address_to_hypercall(
}

/// unlink deletes a name from the filesystem. This is used to handle `unlink` syscalls from the guest.
/// TODO: UNSAFE AS *%@#. It has to be checked that the VM is allowed to unlink that file!
pub fn unlink(mem: &MmapMemory, sysunlink: &mut UnlinkParams) {
unsafe {
sysunlink.ret = libc::unlink(mem.host_address(sysunlink.name).unwrap() as *const i8);
pub fn unlink(mem: &MmapMemory, sysunlink: &mut UnlinkParams, file_map: &mut UhyveFileMap) {
let requested_path = mem.host_address(sysunlink.name).unwrap() as *const i8;
if let Ok(guest_path) = unsafe { CStr::from_ptr(requested_path) }.to_str() {
if let Some(host_path) = file_map.get_host_path(guest_path) {
// We can safely unwrap here, as host_path.as_bytes will never contain internal \0 bytes
// As host_path_c_string is a valid CString, this implementation is presumed to be safe.
let host_path_c_string = CString::new(host_path.as_bytes()).unwrap();
sysunlink.ret = unsafe { libc::unlink(host_path_c_string.as_c_str().as_ptr()) };
} else {
error!("The kernel requested to unlink() an unknown path ({guest_path}): Rejecting...");
sysunlink.ret = -ENOENT;
}
} else {
error!("The kernel requested to open() a path that is not valid UTF-8. Rejecting...");
sysunlink.ret = -EIO;
}
}

/// 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: &mut UhyveFileMap,
temp_dir_base: &TempDir,
) {
// TODO: Keep track of file descriptors internally, just in case the kernel doesn't close them.
let requested_path_ptr = mem.host_address(sysopen.name).unwrap() as *const i8;
let mut flags = sysopen.flags & ALLOWED_OPEN_FLAGS;
if let Ok(guest_path) = unsafe { CStr::from_ptr(requested_path_ptr) }.to_str() {
if let Some(host_path) = file_map.get_host_path(guest_path) {
// We can safely unwrap here, as host_path.as_bytes will never contain internal \0 bytes
// As host_path_c_string is a valid CString, this implementation is presumed to be safe.
let host_path_c_string = CString::new(host_path.as_bytes()).unwrap();

sysopen.ret = unsafe {
libc::open(
host_path_c_string.as_c_str().as_ptr(),
sysopen.flags,
sysopen.mode,
)
};
} else {
debug!("Attempting to open a temp file for {:#?}...", guest_path);

// See: https://lwn.net/Articles/926782/
// See: https://github.com/hermit-os/kernel/commit/71bc629
if (flags & (O_DIRECTORY | O_CREAT)) == (O_DIRECTORY | O_CREAT) {
error!("An open() call used O_DIRECTORY and O_CREAT at the same time. Aborting...");
sysopen.ret = -EINVAL
}

// Existing files that already exist should be in the file map, not here.
// If a supposed attacker can predict where we open a file and its filename,
// this contigency, together with O_CREAT, will cause the write to fail.
flags |= O_EXCL;

let host_path = temp_dir_base.path().join(guest_path);
let host_path_c_string =
file_map.insert_temporary_file(guest_path, host_path.into_os_string());

let new_host_path = host_path_c_string.as_c_str().as_ptr();
sysopen.ret = unsafe { libc::open(new_host_path, flags, sysopen.mode) };
}
} else {
error!("The kernel requested to open() a path that is not valid UTF-8. Rejecting...");
sysopen.ret = -EINVAL;
}
}

Expand Down
Loading