use std::fmt::Debug;
use std::option::Option;
use std::path::Path;
use std::sync::{Arc, Mutex};
use tracing::{Span, instrument};
use tracing_core::LevelFilter;
use super::host_funcs::{FunctionRegistry, default_writer_func};
use super::snapshot::Snapshot;
use super::uninitialized_evolve::evolve_impl_multi_use;
use crate::func::host_functions::{HostFunction, register_host_function};
use crate::func::{ParameterTuple, SupportedReturnType};
#[cfg(feature = "build-metadata")]
use crate::log_build_details;
use crate::mem::memory_region::{DEFAULT_GUEST_BLOB_MEM_FLAGS, MemoryRegionFlags};
use crate::mem::mgr::SandboxMemoryManager;
#[cfg(feature = "nanvix-unstable")]
use crate::mem::shared_mem::HostSharedMemory;
use crate::mem::shared_mem::{ExclusiveSharedMemory, SharedMemory};
use crate::sandbox::SandboxConfiguration;
use crate::{MultiUseSandbox, Result, new_error};
#[cfg(any(crashdump, gdb))]
#[derive(Clone, Debug, Default)]
pub(crate) struct SandboxRuntimeConfig {
#[cfg(crashdump)]
pub(crate) binary_path: Option<String>,
#[cfg(gdb)]
pub(crate) debug_info: Option<super::config::DebugInfo>,
#[cfg(crashdump)]
pub(crate) guest_core_dump: bool,
#[cfg(crashdump)]
pub(crate) entry_point: Option<u64>,
}
#[cfg(feature = "nanvix-unstable")]
pub struct GuestCounter {
inner: Mutex<GuestCounterInner>,
}
#[cfg(feature = "nanvix-unstable")]
struct GuestCounterInner {
deferred_hshm: Arc<Mutex<Option<HostSharedMemory>>>,
offset: usize,
value: u64,
}
#[cfg(feature = "nanvix-unstable")]
impl core::fmt::Debug for GuestCounter {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("GuestCounter").finish_non_exhaustive()
}
}
#[cfg(feature = "nanvix-unstable")]
impl GuestCounter {
pub fn increment(&self) -> Result<()> {
let mut inner = self.inner.lock().map_err(|e| new_error!("{e}"))?;
let shm = {
let guard = inner.deferred_hshm.lock().map_err(|e| new_error!("{e}"))?;
guard
.as_ref()
.ok_or_else(|| {
new_error!("GuestCounter cannot be used before shared memory is built")
})?
.clone()
};
let new_value = inner
.value
.checked_add(1)
.ok_or_else(|| new_error!("GuestCounter overflow"))?;
shm.write::<u64>(inner.offset, new_value)?;
inner.value = new_value;
Ok(())
}
pub fn decrement(&self) -> Result<()> {
let mut inner = self.inner.lock().map_err(|e| new_error!("{e}"))?;
let shm = {
let guard = inner.deferred_hshm.lock().map_err(|e| new_error!("{e}"))?;
guard
.as_ref()
.ok_or_else(|| {
new_error!("GuestCounter cannot be used before shared memory is built")
})?
.clone()
};
let new_value = inner
.value
.checked_sub(1)
.ok_or_else(|| new_error!("GuestCounter underflow"))?;
shm.write::<u64>(inner.offset, new_value)?;
inner.value = new_value;
Ok(())
}
pub fn value(&self) -> Result<u64> {
let inner = self.inner.lock().map_err(|e| new_error!("{e}"))?;
Ok(inner.value)
}
}
pub struct UninitializedSandbox {
pub(crate) host_funcs: Arc<Mutex<FunctionRegistry>>,
pub(crate) mgr: SandboxMemoryManager<ExclusiveSharedMemory>,
pub(crate) max_guest_log_level: Option<LevelFilter>,
pub(crate) config: SandboxConfiguration,
#[cfg(any(crashdump, gdb))]
pub(crate) rt_cfg: SandboxRuntimeConfig,
pub(crate) load_info: crate::mem::exe::LoadInfo,
pub(crate) stack_top_gva: u64,
#[cfg(feature = "nanvix-unstable")]
pub(crate) deferred_hshm: Arc<Mutex<Option<HostSharedMemory>>>,
#[cfg(feature = "nanvix-unstable")]
counter_taken: std::sync::atomic::AtomicBool,
pub(crate) pending_file_mappings: Vec<super::file_mapping::PreparedFileMapping>,
}
impl Debug for UninitializedSandbox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UninitializedSandbox")
.field("memory_layout", &self.mgr.layout)
.finish()
}
}
#[derive(Debug)]
pub enum GuestBinary<'a> {
Buffer(&'a [u8]),
FilePath(String),
}
impl<'a> GuestBinary<'a> {
pub fn canonicalize(&mut self) -> Result<()> {
if let GuestBinary::FilePath(p) = self {
let canon = Path::new(&p)
.canonicalize()
.map_err(|e| new_error!("GuestBinary not found: '{}': {}", p, e))?
.into_os_string()
.into_string()
.map_err(|e| new_error!("Error converting OsString to String: {:?}", e))?;
*self = GuestBinary::FilePath(canon)
}
Ok(())
}
}
#[derive(Debug)]
pub struct GuestBlob<'a> {
pub data: &'a [u8],
pub permissions: MemoryRegionFlags,
}
impl<'a> From<&'a [u8]> for GuestBlob<'a> {
fn from(data: &'a [u8]) -> Self {
GuestBlob {
data,
permissions: DEFAULT_GUEST_BLOB_MEM_FLAGS,
}
}
}
#[derive(Debug)]
pub struct GuestEnvironment<'a, 'b> {
pub guest_binary: GuestBinary<'a>,
pub init_data: Option<GuestBlob<'b>>,
}
impl<'a, 'b> GuestEnvironment<'a, 'b> {
pub fn new(guest_binary: GuestBinary<'a>, init_data: Option<&'b [u8]>) -> Self {
GuestEnvironment {
guest_binary,
init_data: init_data.map(GuestBlob::from),
}
}
}
impl<'a> From<GuestBinary<'a>> for GuestEnvironment<'a, '_> {
fn from(guest_binary: GuestBinary<'a>) -> Self {
GuestEnvironment {
guest_binary,
init_data: None,
}
}
}
impl UninitializedSandbox {
#[cfg(feature = "nanvix-unstable")]
pub fn guest_counter(&mut self) -> Result<GuestCounter> {
use std::sync::atomic::Ordering;
use hyperlight_common::layout::SCRATCH_TOP_GUEST_COUNTER_OFFSET;
if self.counter_taken.swap(true, Ordering::Relaxed) {
return Err(new_error!(
"GuestCounter has already been created for this sandbox"
));
}
let scratch_size = self.mgr.scratch_mem.mem_size();
if (SCRATCH_TOP_GUEST_COUNTER_OFFSET as usize) > scratch_size {
return Err(new_error!(
"scratch memory too small for guest counter (size {:#x}, need offset {:#x})",
scratch_size,
SCRATCH_TOP_GUEST_COUNTER_OFFSET,
));
}
let offset = scratch_size - SCRATCH_TOP_GUEST_COUNTER_OFFSET as usize;
let deferred_hshm = self.deferred_hshm.clone();
Ok(GuestCounter {
inner: Mutex::new(GuestCounterInner {
deferred_hshm,
offset,
value: 0,
}),
})
}
fn from_snapshot(
snapshot: Arc<Snapshot>,
cfg: Option<SandboxConfiguration>,
#[cfg(crashdump)] binary_path: Option<String>,
) -> Result<Self> {
#[cfg(feature = "build-metadata")]
log_build_details();
#[cfg(target_os = "windows")]
check_windows_version()?;
let sandbox_cfg = cfg.unwrap_or_default();
#[cfg(any(crashdump, gdb))]
let rt_cfg = {
#[cfg(crashdump)]
let guest_core_dump = sandbox_cfg.get_guest_core_dump();
#[cfg(gdb)]
let debug_info = sandbox_cfg.get_guest_debug_info();
SandboxRuntimeConfig {
#[cfg(crashdump)]
binary_path,
#[cfg(gdb)]
debug_info,
#[cfg(crashdump)]
guest_core_dump,
#[cfg(crashdump)]
entry_point: None,
}
};
let mem_mgr_wrapper =
SandboxMemoryManager::<ExclusiveSharedMemory>::from_snapshot(snapshot.as_ref())?;
let host_funcs = Arc::new(Mutex::new(FunctionRegistry::default()));
let mut sandbox = Self {
host_funcs,
mgr: mem_mgr_wrapper,
max_guest_log_level: None,
config: sandbox_cfg,
#[cfg(any(crashdump, gdb))]
rt_cfg,
load_info: snapshot.load_info(),
stack_top_gva: snapshot.stack_top_gva(),
#[cfg(feature = "nanvix-unstable")]
deferred_hshm: Arc::new(Mutex::new(None)),
#[cfg(feature = "nanvix-unstable")]
counter_taken: std::sync::atomic::AtomicBool::new(false),
pending_file_mappings: Vec::new(),
};
sandbox.register_print(default_writer_func)?;
crate::debug!("Sandbox created: {:#?}", sandbox);
Ok(sandbox)
}
#[instrument(
err(Debug),
skip(env),
parent = Span::current()
)]
pub fn new<'a, 'b>(
env: impl Into<GuestEnvironment<'a, 'b>>,
cfg: Option<SandboxConfiguration>,
) -> Result<Self> {
let cfg = cfg.unwrap_or_default();
let env = env.into();
#[cfg(crashdump)]
let binary_path = match &env.guest_binary {
GuestBinary::FilePath(path) => Some(path.clone()),
GuestBinary::Buffer(_) => None,
};
let snapshot = Snapshot::from_env(env, cfg)?;
Self::from_snapshot(
Arc::new(snapshot),
Some(cfg),
#[cfg(crashdump)]
binary_path,
)
}
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
pub fn evolve(self) -> Result<MultiUseSandbox> {
evolve_impl_multi_use(self)
}
#[instrument(err(Debug), skip(self, file_path, guest_base, label), parent = Span::current())]
pub fn map_file_cow(
&mut self,
file_path: &std::path::Path,
guest_base: u64,
label: Option<&str>,
) -> crate::Result<u64> {
if self.pending_file_mappings.len() >= hyperlight_common::mem::MAX_FILE_MAPPINGS {
return Err(crate::HyperlightError::Error(format!(
"map_file_cow: file mapping limit reached ({} of {})",
self.pending_file_mappings.len(),
hyperlight_common::mem::MAX_FILE_MAPPINGS,
)));
}
let shared_size = self.mgr.shared_mem.mem_size() as u64;
let base_addr = crate::mem::layout::SandboxMemoryLayout::BASE_ADDRESS as u64;
let prepared = super::file_mapping::prepare_file_cow(file_path, guest_base, label)?;
let mapping_end = guest_base
.checked_add(prepared.size as u64)
.ok_or_else(|| {
crate::HyperlightError::Error(format!(
"map_file_cow: guest address overflow: {:#x} + {:#x}",
guest_base, prepared.size
))
})?;
let shared_end = base_addr.checked_add(shared_size).ok_or_else(|| {
crate::HyperlightError::Error("shared memory end overflow".to_string())
})?;
if guest_base < shared_end && mapping_end > base_addr {
return Err(crate::HyperlightError::Error(format!(
"map_file_cow: mapping [{:#x}..{:#x}) overlaps sandbox shared memory [{:#x}..{:#x})",
guest_base, mapping_end, base_addr, shared_end,
)));
}
let size = prepared.size as u64;
let new_start = guest_base;
let new_end = mapping_end;
for existing in &self.pending_file_mappings {
let ex_start = existing.guest_base;
let ex_end = ex_start.checked_add(existing.size as u64).ok_or_else(|| {
crate::HyperlightError::Error(format!(
"map_file_cow: existing mapping address overflow: {:#x} + {:#x}",
ex_start, existing.size
))
})?;
if new_start < ex_end && new_end > ex_start {
return Err(crate::HyperlightError::Error(format!(
"map_file_cow: mapping [{:#x}..{:#x}) overlaps existing mapping [{:#x}..{:#x})",
new_start, new_end, ex_start, ex_end,
)));
}
}
self.pending_file_mappings.push(prepared);
Ok(size)
}
pub fn shared_mem_size(&self) -> usize {
self.mgr.shared_mem.mem_size()
}
pub fn set_max_guest_log_level(&mut self, log_level: LevelFilter) {
self.max_guest_log_level = Some(log_level);
}
pub fn register<Args: ParameterTuple, Output: SupportedReturnType>(
&mut self,
name: impl AsRef<str>,
host_func: impl Into<HostFunction<Output, Args>>,
) -> Result<()> {
register_host_function(host_func, self, name.as_ref())
}
pub fn register_print(
&mut self,
print_func: impl Into<HostFunction<i32, (String,)>>,
) -> Result<()> {
self.register("HostPrint", print_func)
}
#[cfg(all(test, feature = "nanvix-unstable"))]
fn simulate_build(&self) {
let hshm = self.mgr.scratch_mem.as_host_shared_memory();
#[allow(clippy::unwrap_used)]
{
*self.deferred_hshm.lock().unwrap() = Some(hshm);
}
}
}
#[cfg(target_os = "windows")]
fn check_windows_version() -> Result<()> {
use windows_version::{OsVersion, is_server};
const WINDOWS_MAJOR: u32 = 10;
const WINDOWS_MINOR: u32 = 0;
const WINDOWS_PACK: u32 = 0;
if is_server() {
if OsVersion::current() < OsVersion::new(WINDOWS_MAJOR, WINDOWS_MINOR, WINDOWS_PACK, 20348)
{
return Err(new_error!(
"Hyperlight Requires Windows Server 2022 or newer"
));
}
} else if OsVersion::current()
< OsVersion::new(WINDOWS_MAJOR, WINDOWS_MINOR, WINDOWS_PACK, 22000)
{
return Err(new_error!("Hyperlight Requires Windows 11 or newer"));
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::sync::mpsc::channel;
use std::{fs, thread};
use crossbeam_queue::ArrayQueue;
use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnValue};
use hyperlight_testing::simple_guest_as_string;
use crate::sandbox::SandboxConfiguration;
use crate::sandbox::uninitialized::{GuestBinary, GuestEnvironment};
use crate::{MultiUseSandbox, Result, UninitializedSandbox, new_error};
#[test]
fn test_load_extra_blob() {
let binary_path = simple_guest_as_string().unwrap();
let buffer = [0xde, 0xad, 0xbe, 0xef];
let guest_env =
GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), Some(&buffer));
let uninitialized_sandbox = UninitializedSandbox::new(guest_env, None).unwrap();
let mut sandbox: MultiUseSandbox = uninitialized_sandbox.evolve().unwrap();
let res = sandbox
.call::<Vec<u8>>("ReadFromUserMemory", (4u64, buffer.to_vec()))
.expect("Failed to call ReadFromUserMemory");
assert_eq!(res, buffer.to_vec());
}
#[test]
fn test_new_sandbox() {
let binary_path = simple_guest_as_string().unwrap();
let sandbox = UninitializedSandbox::new(GuestBinary::FilePath(binary_path.clone()), None);
assert!(sandbox.is_ok());
let mut binary_path_does_not_exist = binary_path.clone();
binary_path_does_not_exist.push_str(".nonexistent");
let uninitialized_sandbox =
UninitializedSandbox::new(GuestBinary::FilePath(binary_path_does_not_exist), None);
assert!(uninitialized_sandbox.is_err());
let cfg = {
let mut cfg = SandboxConfiguration::default();
cfg.set_input_data_size(0x1000);
cfg.set_output_data_size(0x1000);
cfg.set_heap_size(0x1000);
Some(cfg)
};
let uninitialized_sandbox =
UninitializedSandbox::new(GuestBinary::FilePath(binary_path.clone()), cfg);
assert!(uninitialized_sandbox.is_ok());
let uninitialized_sandbox =
UninitializedSandbox::new(GuestBinary::FilePath(binary_path), None).unwrap();
let _sandbox: MultiUseSandbox = uninitialized_sandbox.evolve().unwrap();
let binary_path = simple_guest_as_string().unwrap();
let sandbox =
UninitializedSandbox::new(GuestBinary::Buffer(&fs::read(binary_path).unwrap()), None);
assert!(sandbox.is_ok());
let binary_path = simple_guest_as_string().unwrap();
let mut bytes = fs::read(binary_path).unwrap();
let _ = bytes.split_off(100);
let sandbox = UninitializedSandbox::new(GuestBinary::Buffer(&bytes), None);
assert!(sandbox.is_err());
}
#[test]
fn test_host_functions() {
let uninitialized_sandbox = || {
UninitializedSandbox::new(
GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
None,
)
.unwrap()
};
{
let mut usbox = uninitialized_sandbox();
usbox.register("test0", |arg: i32| Ok(arg + 1)).unwrap();
let sandbox: Result<MultiUseSandbox> = usbox.evolve();
assert!(sandbox.is_ok());
let sandbox = sandbox.unwrap();
let host_funcs = sandbox
.host_funcs
.try_lock()
.map_err(|_| new_error!("Error locking"));
assert!(host_funcs.is_ok());
let res = host_funcs
.unwrap()
.call_host_function("test0", vec![ParameterValue::Int(1)])
.unwrap();
assert_eq!(res, ReturnValue::Int(2));
}
{
let mut usbox = uninitialized_sandbox();
usbox.register("test1", |a: i32, b: i32| Ok(a + b)).unwrap();
let sandbox: Result<MultiUseSandbox> = usbox.evolve();
assert!(sandbox.is_ok());
let sandbox = sandbox.unwrap();
let host_funcs = sandbox
.host_funcs
.try_lock()
.map_err(|_| new_error!("Error locking"));
assert!(host_funcs.is_ok());
let res = host_funcs
.unwrap()
.call_host_function(
"test1",
vec![ParameterValue::Int(1), ParameterValue::Int(2)],
)
.unwrap();
assert_eq!(res, ReturnValue::Int(3));
}
{
let mut usbox = uninitialized_sandbox();
usbox
.register("test2", |msg: String| {
println!("test2 called: {}", msg);
Ok(())
})
.unwrap();
let sandbox: Result<MultiUseSandbox> = usbox.evolve();
assert!(sandbox.is_ok());
let sandbox = sandbox.unwrap();
let host_funcs = sandbox
.host_funcs
.try_lock()
.map_err(|_| new_error!("Error locking"));
assert!(host_funcs.is_ok());
let res = host_funcs.unwrap().call_host_function("test2", vec![]);
assert!(res.is_err());
}
{
let usbox = uninitialized_sandbox();
let sandbox: Result<MultiUseSandbox> = usbox.evolve();
assert!(sandbox.is_ok());
let sandbox = sandbox.unwrap();
let host_funcs = sandbox
.host_funcs
.try_lock()
.map_err(|_| new_error!("Error locking"));
assert!(host_funcs.is_ok());
let res = host_funcs.unwrap().call_host_function("test4", vec![]);
assert!(res.is_err());
}
}
#[test]
fn test_host_print() {
let (tx, rx) = channel();
let writer = move |msg| {
let _ = tx.send(msg);
Ok(0)
};
let mut sandbox = UninitializedSandbox::new(
GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
None,
)
.expect("Failed to create sandbox");
sandbox
.register_print(writer)
.expect("Failed to register host print function");
let host_funcs = sandbox
.host_funcs
.try_lock()
.map_err(|_| new_error!("Error locking"));
assert!(host_funcs.is_ok());
host_funcs.unwrap().host_print("test".to_string()).unwrap();
drop(sandbox);
let received_msgs: Vec<_> = rx.into_iter().collect();
assert_eq!(received_msgs, ["test"]);
fn fn_writer(msg: String) -> Result<i32> {
assert_eq!(msg, "test2");
Ok(0)
}
let mut sandbox = UninitializedSandbox::new(
GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
None,
)
.expect("Failed to create sandbox");
sandbox
.register_print(fn_writer)
.expect("Failed to register host print function");
let host_funcs = sandbox
.host_funcs
.try_lock()
.map_err(|_| new_error!("Error locking"));
assert!(host_funcs.is_ok());
host_funcs.unwrap().host_print("test2".to_string()).unwrap();
let mut test_host_print = TestHostPrint::new();
let writer_closure = move |s| test_host_print.write(s);
let mut sandbox = UninitializedSandbox::new(
GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
None,
)
.expect("Failed to create sandbox");
sandbox
.register_print(writer_closure)
.expect("Failed to register host print function");
let host_funcs = sandbox
.host_funcs
.try_lock()
.map_err(|_| new_error!("Error locking"));
assert!(host_funcs.is_ok());
host_funcs.unwrap().host_print("test3".to_string()).unwrap();
}
struct TestHostPrint {}
impl TestHostPrint {
fn new() -> Self {
TestHostPrint {}
}
fn write(&mut self, msg: String) -> Result<i32> {
assert_eq!(msg, "test3");
Ok(0)
}
}
#[test]
fn check_create_and_use_sandbox_on_different_threads() {
let unintializedsandbox_queue = Arc::new(ArrayQueue::<UninitializedSandbox>::new(10));
let sandbox_queue = Arc::new(ArrayQueue::<MultiUseSandbox>::new(10));
for i in 0..10 {
let simple_guest_path = simple_guest_as_string().expect("Guest Binary Missing");
let unintializedsandbox = {
let err_string = format!("failed to create UninitializedSandbox {i}");
let err_str = err_string.as_str();
UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_path), None)
.expect(err_str)
};
{
let err_string = format!("Failed to push UninitializedSandbox {i}");
let err_str = err_string.as_str();
unintializedsandbox_queue
.push(unintializedsandbox)
.expect(err_str);
}
}
let thread_handles = (0..10)
.map(|i| {
let uq = unintializedsandbox_queue.clone();
let sq = sandbox_queue.clone();
thread::spawn(move || {
let uninitialized_sandbox = uq.pop().unwrap_or_else(|| {
panic!("Failed to pop UninitializedSandbox thread {}", i)
});
let host_funcs = uninitialized_sandbox
.host_funcs
.try_lock()
.map_err(|_| new_error!("Error locking"));
assert!(host_funcs.is_ok());
host_funcs
.unwrap()
.host_print(format!("Print from UninitializedSandbox on Thread {}\n", i))
.unwrap();
let sandbox = uninitialized_sandbox.evolve().unwrap_or_else(|_| {
panic!("Failed to initialize UninitializedSandbox thread {}", i)
});
sq.push(sandbox).unwrap_or_else(|_| {
panic!("Failed to push UninitializedSandbox thread {}", i)
})
})
})
.collect::<Vec<_>>();
for handle in thread_handles {
handle.join().unwrap();
}
let thread_handles = (0..10)
.map(|i| {
let sq = sandbox_queue.clone();
thread::spawn(move || {
let sandbox = sq
.pop()
.unwrap_or_else(|| panic!("Failed to pop Sandbox thread {}", i));
let host_funcs = sandbox
.host_funcs
.try_lock()
.map_err(|_| new_error!("Error locking"));
assert!(host_funcs.is_ok());
host_funcs
.unwrap()
.host_print(format!("Print from Sandbox on Thread {}\n", i))
.unwrap();
})
})
.collect::<Vec<_>>();
for handle in thread_handles {
handle.join().unwrap();
}
}
#[test]
#[cfg(feature = "build-metadata")]
fn test_trace_trace() {
use hyperlight_testing::tracing_subscriber::TracingSubscriber;
use tracing::Level;
use tracing_core::Subscriber;
use tracing_core::callsite::rebuild_interest_cache;
use uuid::Uuid;
fn get_span_attr<'a>(span: &'a serde_json::Value, key: &str) -> Option<&'a str> {
span.get("span")?.get("attributes")?.get(key)?.as_str()
}
fn get_event_field<'a>(event: &'a serde_json::Value, field: &str) -> Option<&'a str> {
event.get("event")?.get(field)?.as_str()
}
fn get_event_metadata<'a>(event: &'a serde_json::Value, field: &str) -> Option<&'a str> {
event.get("event")?.get("metadata")?.get(field)?.as_str()
}
let subscriber = TracingSubscriber::new(Level::TRACE);
tracing::subscriber::with_default(subscriber.clone(), || {
let bad_path = simple_guest_as_string().unwrap() + "does_not_exist";
let _ = UninitializedSandbox::new(GuestBinary::FilePath(bad_path.clone()), None);
rebuild_interest_cache();
subscriber.clear();
let correlation_id = Uuid::new_v4().to_string();
let _span = tracing::error_span!("test_trace_logs", %correlation_id).entered();
let (test_span_id, span_meta) = subscriber
.current_span()
.into_inner()
.expect("Should be inside a span");
assert_eq!(span_meta.name(), "test_trace_logs");
let span_data = subscriber.get_span(test_span_id.into_u64());
let recorded_id =
get_span_attr(&span_data, "correlation_id").expect("correlation_id not found");
assert_eq!(recorded_id, correlation_id);
let result = UninitializedSandbox::new(GuestBinary::FilePath(bad_path), None);
assert!(result.is_err(), "Sandbox creation should fail");
let (current_id, _) = subscriber
.current_span()
.into_inner()
.expect("Should still be inside a span");
assert_eq!(
current_id.into_u64(),
test_span_id.into_u64(),
"Should still be in the test span"
);
let all_spans = subscriber.get_all_spans();
let _new_span_entry = all_spans
.iter()
.find(|&(&id, _)| {
id != test_span_id.into_u64()
&& subscriber.get_span_metadata(id).name() == "new"
})
.expect("Expected a span named 'new' from UninitializedSandbox::new");
let events = subscriber.get_events();
assert_eq!(events.len(), 1, "Expected exactly one error event");
let event = &events[0];
let level = get_event_metadata(event, "level").expect("event should have level");
let error = get_event_field(event, "error").expect("event should have error field");
let target = get_event_metadata(event, "target").expect("event should have target");
let module_path =
get_event_metadata(event, "module_path").expect("event should have module_path");
assert_eq!(level, "ERROR");
assert!(
error.contains("GuestBinary not found"),
"Error should mention 'GuestBinary not found', got: {error}"
);
assert_eq!(target, "hyperlight_host::sandbox::uninitialized");
assert_eq!(module_path, "hyperlight_host::sandbox::uninitialized");
});
}
#[test]
#[ignore]
#[cfg(feature = "build-metadata")]
fn test_log_trace() {
use std::path::PathBuf;
use hyperlight_testing::logger::{LOGGER as TEST_LOGGER, Logger as TestLogger};
use log::Level;
use tracing_core::callsite::rebuild_interest_cache;
{
TestLogger::initialize_test_logger();
TEST_LOGGER.set_max_level(log::LevelFilter::Trace);
rebuild_interest_cache();
let mut invalid_binary_path = simple_guest_as_string().unwrap();
invalid_binary_path.push_str("does_not_exist");
let sbox = UninitializedSandbox::new(GuestBinary::FilePath(invalid_binary_path), None);
assert!(sbox.is_err());
let num_calls = TEST_LOGGER.num_log_calls();
assert_eq!(13, num_calls);
let logcall = TEST_LOGGER.get_log_call(0).unwrap();
assert_eq!(Level::Info, logcall.level);
assert!(logcall.args.starts_with("new; cfg"));
assert_eq!("hyperlight_host::sandbox::uninitialized", logcall.target);
let logcall = TEST_LOGGER.get_log_call(1).unwrap();
assert_eq!(Level::Trace, logcall.level);
assert_eq!(logcall.args, "-> new;");
assert_eq!("tracing::span::active", logcall.target);
let logcall = TEST_LOGGER.get_log_call(10).unwrap();
assert_eq!(Level::Error, logcall.level);
assert!(
logcall
.args
.starts_with("error=Error(\"GuestBinary not found:")
);
assert_eq!("hyperlight_host::sandbox::uninitialized", logcall.target);
let logcall = TEST_LOGGER.get_log_call(11).unwrap();
assert_eq!(Level::Trace, logcall.level);
assert_eq!(logcall.args, "<- new;");
assert_eq!("tracing::span::active", logcall.target);
let logcall = TEST_LOGGER.get_log_call(12).unwrap();
assert_eq!(Level::Trace, logcall.level);
assert_eq!(logcall.args, "-- new;");
assert_eq!("tracing::span", logcall.target);
}
{
TEST_LOGGER.clear_log_calls();
TEST_LOGGER.set_max_level(log::LevelFilter::Info);
let mut valid_binary_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
valid_binary_path.push("src");
valid_binary_path.push("sandbox");
valid_binary_path.push("initialized.rs");
let sbox = UninitializedSandbox::new(
GuestBinary::FilePath(valid_binary_path.into_os_string().into_string().unwrap()),
None,
);
assert!(sbox.is_err());
let num_calls = TEST_LOGGER.num_log_calls();
assert_eq!(2, num_calls);
let logcall = TEST_LOGGER.get_log_call(0).unwrap();
assert_eq!(Level::Info, logcall.level);
assert!(logcall.args.starts_with("new; cfg"));
assert_eq!("hyperlight_host::sandbox::uninitialized", logcall.target);
let logcall = TEST_LOGGER.get_log_call(1).unwrap();
assert_eq!(Level::Error, logcall.level);
assert!(
logcall
.args
.starts_with("error=Error(\"GuestBinary not found:")
);
assert_eq!("hyperlight_host::sandbox::uninitialized", logcall.target);
}
{
TEST_LOGGER.clear_log_calls();
TEST_LOGGER.set_max_level(log::LevelFilter::Error);
let sbox = {
let res = UninitializedSandbox::new(
GuestBinary::FilePath(simple_guest_as_string().unwrap()),
None,
);
res.unwrap()
};
let _: Result<MultiUseSandbox> = sbox.evolve();
let num_calls = TEST_LOGGER.num_log_calls();
assert_eq!(0, num_calls);
}
}
#[test]
fn test_invalid_path() {
let invalid_path = "some/path/that/does/not/exist";
let sbox = UninitializedSandbox::new(GuestBinary::FilePath(invalid_path.to_string()), None);
println!("{:?}", sbox);
#[cfg(target_os = "windows")]
assert!(
matches!(sbox, Err(e) if e.to_string().contains("GuestBinary not found: 'some/path/that/does/not/exist': The system cannot find the path specified. (os error 3)"))
);
#[cfg(target_os = "linux")]
assert!(
matches!(sbox, Err(e) if e.to_string().contains("GuestBinary not found: 'some/path/that/does/not/exist': No such file or directory (os error 2)"))
);
}
#[test]
fn test_from_snapshot_various_configurations() {
use crate::sandbox::snapshot::Snapshot;
let binary_path = simple_guest_as_string().unwrap();
{
let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None);
let snapshot = Arc::new(
Snapshot::from_env(env, Default::default())
.expect("Failed to create snapshot with default config"),
);
let sandbox1 = UninitializedSandbox::from_snapshot(
snapshot.clone(),
None,
#[cfg(crashdump)]
Some(binary_path.clone()),
)
.expect("Failed to create first sandbox from snapshot");
let sandbox2 = UninitializedSandbox::from_snapshot(
snapshot.clone(),
None,
#[cfg(crashdump)]
Some(binary_path.clone()),
)
.expect("Failed to create second sandbox from snapshot");
let _evolved1: MultiUseSandbox = sandbox1.evolve().expect("Failed to evolve sandbox1");
let _evolved2: MultiUseSandbox = sandbox2.evolve().expect("Failed to evolve sandbox2");
}
{
let mut cfg = SandboxConfiguration::default();
cfg.set_heap_size(16 * 1024 * 1024);
let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None);
let snapshot = Arc::new(
Snapshot::from_env(env, cfg)
.expect("Failed to create snapshot with custom heap size"),
);
let sandbox = UninitializedSandbox::from_snapshot(
snapshot,
None,
#[cfg(crashdump)]
Some(binary_path.clone()),
)
.expect("Failed to create sandbox from snapshot with custom heap");
let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox");
}
{
let mut cfg = SandboxConfiguration::default();
cfg.set_scratch_size(256 * 1024);
let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None);
let snapshot = Arc::new(
Snapshot::from_env(env, cfg)
.expect("Failed to create snapshot with custom stack size"),
);
let sandbox = UninitializedSandbox::from_snapshot(
snapshot,
None,
#[cfg(crashdump)]
Some(binary_path.clone()),
)
.expect("Failed to create sandbox from snapshot with custom stack");
let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox");
}
{
let mut cfg = SandboxConfiguration::default();
cfg.set_input_data_size(64 * 1024); cfg.set_output_data_size(64 * 1024);
let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None);
let snapshot = Arc::new(
Snapshot::from_env(env, cfg)
.expect("Failed to create snapshot with custom buffer sizes"),
);
let sandbox = UninitializedSandbox::from_snapshot(
snapshot,
None,
#[cfg(crashdump)]
Some(binary_path.clone()),
)
.expect("Failed to create sandbox from snapshot with custom buffers");
let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox");
}
{
let mut cfg = SandboxConfiguration::default();
cfg.set_heap_size(32 * 1024 * 1024); cfg.set_scratch_size(256 * 1024 * 2); cfg.set_input_data_size(128 * 1024); cfg.set_output_data_size(128 * 1024);
let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None);
let snapshot = Arc::new(
Snapshot::from_env(env, cfg)
.expect("Failed to create snapshot with all custom settings"),
);
let sandbox1 = UninitializedSandbox::from_snapshot(
snapshot.clone(),
None,
#[cfg(crashdump)]
Some(binary_path.clone()),
)
.expect("Failed to create sandbox1 from fully customized snapshot");
let sandbox2 = UninitializedSandbox::from_snapshot(
snapshot.clone(),
None,
#[cfg(crashdump)]
Some(binary_path.clone()),
)
.expect("Failed to create sandbox2 from fully customized snapshot");
let sandbox3 = UninitializedSandbox::from_snapshot(
snapshot.clone(),
None,
#[cfg(crashdump)]
Some(binary_path.clone()),
)
.expect("Failed to create sandbox3 from fully customized snapshot");
let _evolved1: MultiUseSandbox = sandbox1.evolve().expect("Failed to evolve sandbox1");
let _evolved2: MultiUseSandbox = sandbox2.evolve().expect("Failed to evolve sandbox2");
let _evolved3: MultiUseSandbox = sandbox3.evolve().expect("Failed to evolve sandbox3");
}
{
let binary_bytes = fs::read(&binary_path).expect("Failed to read binary file");
let snapshot = Arc::new(
Snapshot::from_env(GuestBinary::Buffer(&binary_bytes), Default::default())
.expect("Failed to create snapshot from buffer"),
);
let sandbox = UninitializedSandbox::from_snapshot(
snapshot,
None,
#[cfg(crashdump)]
None,
)
.expect("Failed to create sandbox from buffer-based snapshot");
let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox");
}
{
let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None);
let snapshot = Arc::new(
Snapshot::from_env(env, Default::default()).expect("Failed to create snapshot"),
);
let mut sandbox = UninitializedSandbox::from_snapshot(
snapshot,
None,
#[cfg(crashdump)]
Some(binary_path.clone()),
)
.expect("Failed to create sandbox from snapshot");
sandbox
.register("CustomAdd", |a: i32, b: i32| Ok(a + b))
.expect("Failed to register custom function");
let evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox");
let host_funcs = evolved
.host_funcs
.try_lock()
.expect("Failed to lock host funcs");
let result = host_funcs
.call_host_function(
"CustomAdd",
vec![ParameterValue::Int(10), ParameterValue::Int(20)],
)
.expect("Failed to call CustomAdd");
assert_eq!(result, ReturnValue::Int(30));
}
{
let init_data = [0xCA, 0xFE, 0xBA, 0xBE];
let guest_env =
GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), Some(&init_data));
let snapshot = Arc::new(
Snapshot::from_env(guest_env, Default::default())
.expect("Failed to create snapshot with init data"),
);
let sandbox = UninitializedSandbox::from_snapshot(
snapshot,
None,
#[cfg(crashdump)]
Some(binary_path.clone()),
)
.expect("Failed to create sandbox from snapshot with init data");
let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox");
}
{
let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None);
let orig_snapshot = Arc::new(
Snapshot::from_env(env, Default::default())
.expect("Failed to create snapshot with default config"),
);
let orig_sandbox = UninitializedSandbox::from_snapshot(
orig_snapshot,
None,
#[cfg(crashdump)]
Some(binary_path.clone()),
)
.expect("Failed to create orig_sandbox");
let mut initialized_sandbox = orig_sandbox
.evolve()
.expect("Failed to evolve orig_sandbox");
let new_snapshot = initialized_sandbox
.snapshot()
.expect("Failed to create new_snapshot");
let new_sandbox = UninitializedSandbox::from_snapshot(
new_snapshot,
None,
#[cfg(crashdump)]
Some(binary_path.clone()),
)
.expect("Failed to create new_sandbox");
let _evolved = new_sandbox.evolve().expect("Failed to evolve new_sandbox");
}
}
#[cfg(feature = "nanvix-unstable")]
mod guest_counter_tests {
use hyperlight_testing::simple_guest_as_string;
use crate::UninitializedSandbox;
use crate::sandbox::uninitialized::GuestBinary;
fn make_sandbox() -> UninitializedSandbox {
UninitializedSandbox::new(
GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
None,
)
.expect("Failed to create sandbox")
}
#[test]
fn create_guest_counter() {
let mut sandbox = make_sandbox();
let counter = sandbox.guest_counter();
assert!(counter.is_ok());
}
#[test]
fn only_one_counter_allowed() {
let mut sandbox = make_sandbox();
let _c1 = sandbox.guest_counter().unwrap();
let c2 = sandbox.guest_counter();
assert!(c2.is_err());
}
#[test]
fn fails_before_build() {
let mut sandbox = make_sandbox();
let counter = sandbox.guest_counter().unwrap();
assert!(counter.increment().is_err());
assert!(counter.decrement().is_err());
}
#[test]
fn increment_decrement() {
let mut sandbox = make_sandbox();
let counter = sandbox.guest_counter().unwrap();
sandbox.simulate_build();
counter.increment().unwrap();
assert_eq!(counter.value().unwrap(), 1);
counter.increment().unwrap();
assert_eq!(counter.value().unwrap(), 2);
counter.decrement().unwrap();
assert_eq!(counter.value().unwrap(), 1);
}
#[test]
fn underflow_returns_error() {
let mut sandbox = make_sandbox();
let counter = sandbox.guest_counter().unwrap();
sandbox.simulate_build();
assert_eq!(counter.value().unwrap(), 0);
let result = counter.decrement();
assert!(result.is_err());
}
}
}