use std::path::Path;
use std::sync::{Arc, Mutex};
#[cfg(target_os = "linux")]
use std::time::Duration;
use hyperlight_common::func::{ParameterTuple, SupportedReturnType};
use tracing_core::LevelFilter;
use crate::func::HostFunction;
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
use crate::sandbox::SandboxConfiguration;
#[cfg(gdb)]
use crate::sandbox::config::DebugInfo;
#[cfg(target_arch = "x86_64")]
use crate::sandbox::config::GuestMsrError;
use crate::sandbox::host_funcs::FunctionEntry;
use crate::sandbox::snapshot::Snapshot;
use crate::sandbox::uninitialized::{GuestBlob, GuestEnvironment};
use crate::{
GuestBinary, HostFunctions, MultiUseSandbox as Sandbox, Result, UninitializedSandbox, new_error,
};
enum Source {
GuestBinary(GuestBinary),
Snapshot(Arc<Snapshot>),
}
impl Source {
fn file(path: impl AsRef<Path>) -> Self {
Self::GuestBinary(GuestBinary::FilePath(path.as_ref().to_path_buf()))
}
fn bytes(buffer: impl Into<Vec<u8>>) -> Self {
Self::GuestBinary(GuestBinary::Buffer(buffer.into()))
}
}
pub struct SandboxBuilder {
source: Source,
cfg: SandboxConfiguration,
host_funcs: HostFunctions,
init_data: Option<(Vec<u8>, MemoryRegionFlags)>,
mapped_file_cow: Vec<(std::path::PathBuf, u64)>,
mapped_memory_regions: Vec<MemoryRegion>,
guest_log_level: Option<LevelFilter>,
}
impl SandboxBuilder {
fn with_source(source: Source) -> Self {
Self {
source,
cfg: SandboxConfiguration::default(),
host_funcs: HostFunctions::default(),
init_data: None,
mapped_file_cow: Vec::new(),
mapped_memory_regions: Vec::new(),
guest_log_level: None,
}
}
pub fn from_file(path: impl AsRef<Path>) -> Self {
Self::with_source(Source::file(path))
}
pub fn from_bytes(buffer: impl Into<Vec<u8>>) -> Self {
Self::with_source(Source::bytes(buffer))
}
pub fn from_snapshot(snapshot: Arc<Snapshot>) -> Self {
Self::with_source(Source::Snapshot(snapshot))
}
pub fn build(self) -> Result<Sandbox> {
let Self {
source,
cfg,
host_funcs,
init_data,
mapped_file_cow,
mapped_memory_regions,
guest_log_level,
} = self;
let mut sandbox = match source {
Source::GuestBinary(guest_binary) => {
let env = GuestEnvironment {
init_data: init_data.as_ref().map(|(data, flags)| GuestBlob {
data,
permissions: *flags,
}),
guest_binary,
};
let mut uninitialized_sandbox = UninitializedSandbox::new(env, Some(cfg))?;
uninitialized_sandbox.host_funcs = Arc::new(Mutex::new(host_funcs.into_inner()));
for (path, guest_base) in mapped_file_cow {
uninitialized_sandbox.map_file_cow(&path, guest_base)?;
}
if let Some(log_level) = guest_log_level {
uninitialized_sandbox.set_max_guest_log_level(log_level);
}
uninitialized_sandbox.evolve()?
}
Source::Snapshot(snapshot) => {
if init_data.is_some() {
return Err(new_error!(
"init_data has no effect when building from a snapshot, as the snapshot already contains it"
));
}
if guest_log_level.is_some() {
return Err(new_error!(
"guest_log_level has no effect when building from a snapshot, as the snapshot already contains it"
));
}
let mut sandbox = Sandbox::from_snapshot(snapshot, host_funcs, Some(cfg))?;
for (path, guest_base) in mapped_file_cow {
sandbox.map_file_cow(&path, guest_base)?;
}
sandbox
}
};
for region in mapped_memory_regions {
unsafe { sandbox.map_region(®ion)? };
}
Ok(sandbox)
}
}
impl SandboxBuilder {
pub fn init_data(mut self, data: impl Into<Vec<u8>>, flags: MemoryRegionFlags) -> Self {
self.init_data = Some((data.into(), flags));
self
}
pub fn mapped_file_cow(mut self, path: impl AsRef<Path>, guest_base: u64) -> Self {
self.mapped_file_cow
.push((path.as_ref().to_path_buf(), guest_base));
self
}
pub unsafe fn mapped_memory_region(mut self, region: MemoryRegion) -> Self {
self.mapped_memory_regions.push(region);
self
}
pub fn guest_log_level(mut self, level: LevelFilter) -> Self {
self.guest_log_level = Some(level);
self
}
pub fn get_guest_log_level(&self) -> Option<LevelFilter> {
self.guest_log_level
}
}
impl SandboxBuilder {
pub fn host_function<Args: ParameterTuple, Output: SupportedReturnType>(
mut self,
name: impl AsRef<str>,
host_func: impl Into<HostFunction<Output, Args>>,
) -> Self {
let func = host_func.into().into();
let name = name.as_ref().to_string();
let entry = FunctionEntry {
function: func,
parameter_types: Args::TYPE,
return_type: Output::TYPE,
};
self.host_funcs
.inner_mut()
.register_host_function(name, entry);
self
}
pub fn host_print(self, print_func: impl Into<HostFunction<i32, (String,)>>) -> Self {
self.host_function("HostPrint", print_func)
}
pub fn host_functions(mut self, host_funcs: HostFunctions) -> Self {
for (func_name, func_entry) in host_funcs.into_iter() {
self.host_funcs
.inner_mut()
.register_host_function(func_name, func_entry);
}
self
}
}
impl SandboxBuilder {
pub fn input_data_size(mut self, size: usize) -> Self {
self.cfg.set_input_data_size(size);
self
}
pub fn get_input_data_size(&self) -> usize {
self.cfg.get_input_data_size()
}
pub fn output_data_size(mut self, size: usize) -> Self {
self.cfg.set_output_data_size(size);
self
}
pub fn get_output_data_size(&self) -> usize {
self.cfg.get_output_data_size()
}
pub fn heap_size(mut self, size: u64) -> Self {
self.cfg.set_heap_size(size);
self
}
pub fn get_heap_size(&self) -> u64 {
self.cfg.get_heap_size()
}
pub fn scratch_size(mut self, size: usize) -> Self {
self.cfg.set_scratch_size(size);
self
}
pub fn get_scratch_size(&self) -> usize {
self.cfg.get_scratch_size()
}
#[cfg(target_arch = "x86_64")]
pub fn guest_msrs(mut self, indices: &[u32]) -> std::result::Result<Self, GuestMsrError> {
self.cfg.guest_msrs(indices)?;
Ok(self)
}
#[cfg(target_os = "linux")]
pub fn interrupt_retry_delay(mut self, delay: Duration) -> Self {
self.cfg.set_interrupt_retry_delay(delay);
self
}
#[cfg(target_os = "linux")]
pub fn get_interrupt_retry_delay(&self) -> Duration {
self.cfg.get_interrupt_retry_delay()
}
#[cfg(target_os = "linux")]
pub fn interrupt_vcpu_sigrtmin_offset(mut self, offset: u8) -> Result<Self> {
self.cfg.set_interrupt_vcpu_sigrtmin_offset(offset)?;
Ok(self)
}
#[cfg(target_os = "linux")]
pub fn get_interrupt_vcpu_sigrtmin_offset(&self) -> u8 {
self.cfg.get_interrupt_vcpu_sigrtmin_offset()
}
#[cfg(crashdump)]
pub fn guest_core_dump(mut self, enabled: bool) -> Self {
self.cfg.set_guest_core_dump(enabled);
self
}
#[cfg(crashdump)]
pub fn get_guest_core_dump(&self) -> bool {
self.cfg.get_guest_core_dump()
}
#[cfg(gdb)]
pub fn guest_debug_info(mut self, debug_info: DebugInfo) -> Self {
self.cfg.set_guest_debug_info(debug_info);
self
}
#[cfg(gdb)]
pub fn get_guest_debug_info(&self) -> Option<DebugInfo> {
self.cfg.get_guest_debug_info()
}
}
#[cfg(test)]
mod tests {
use hyperlight_testing::simple_guest_as_string;
use tracing_core::LevelFilter;
use super::SandboxBuilder;
use crate::mem::memory_region::MemoryRegionFlags;
#[test]
fn build_from_file() {
let path = simple_guest_as_string().unwrap();
let mut sandbox = SandboxBuilder::from_file(path)
.input_data_size(0x8000)
.build()
.unwrap();
let result = sandbox.call::<String>("Echo", "hello".to_string()).unwrap();
assert_eq!(result, "hello");
}
#[test]
fn build_from_bytes() {
let bytes = std::fs::read(simple_guest_as_string().unwrap()).unwrap();
let mut sandbox = SandboxBuilder::from_bytes(bytes).build().unwrap();
let result = sandbox.call::<String>("Echo", "hello".to_string()).unwrap();
assert_eq!(result, "hello");
}
#[test]
fn build_from_snapshot() {
let path = simple_guest_as_string().unwrap();
let mut sandbox = SandboxBuilder::from_file(path).build().unwrap();
let snapshot = sandbox.snapshot().unwrap();
let mut restored = SandboxBuilder::from_snapshot(snapshot).build().unwrap();
let result = restored
.call::<String>("Echo", "hello".to_string())
.unwrap();
assert_eq!(result, "hello");
}
#[test]
fn build_from_snapshot_errors_on_ignored_settings() {
let path = simple_guest_as_string().unwrap();
let mut sandbox = SandboxBuilder::from_file(path).build().unwrap();
let snapshot = sandbox.snapshot().unwrap();
assert!(
SandboxBuilder::from_snapshot(snapshot.clone())
.init_data([0u8; 8], MemoryRegionFlags::READ)
.build()
.is_err()
);
assert!(
SandboxBuilder::from_snapshot(snapshot)
.guest_log_level(LevelFilter::INFO)
.build()
.is_err()
);
}
}