From 32fbfa2f3d81cd4504df29a3260a804d86bf6896 Mon Sep 17 00:00:00 2001 From: Jorge Prendes Date: Tue, 18 Aug 2026 22:23:08 +0100 Subject: [PATCH] Use SandboxBuilder across examples, tests and docs `SandboxBuilder` is the entry point for creating a sandbox, so examples, benchmarks, fuzz targets, integration tests and documentation all go through it. Reshape the shared test helpers in `tests/common` around the builder: `build_rust_sandbox`, `with_rust_sandbox_from`, `with_c_sandbox_from` and `with_all_guests` replace the helpers that handed out an `UninitializedSandbox`. Call sites that test internals below the public API keep using `UninitializedSandbox` and `SandboxConfiguration` directly. So does `wit_test`, because the generated `Test::instantiate` takes an `UninitializedSandbox`. Signed-off-by: Jorge Prendes --- README.md | 17 +- docs/how-to-debug-a-hyperlight-guest.md | 11 +- docs/msr.md | 4 +- fuzz/fuzz_targets/guest_call.rs | 14 +- fuzz/fuzz_targets/guest_trace.rs | 15 +- fuzz/fuzz_targets/host_call.rs | 21 +- fuzz/fuzz_targets/host_print.rs | 13 +- src/hyperlight_host/benches/benchmarks.rs | 105 +- .../examples/crashdump/main.rs | 53 +- src/hyperlight_host/examples/func_ctx/main.rs | 8 +- .../examples/guest-debugging/main.rs | 78 +- .../examples/hello-world/main.rs | 27 +- src/hyperlight_host/examples/logging/main.rs | 18 +- .../examples/map-file-cow-test/main.rs | 30 +- src/hyperlight_host/examples/metrics/main.rs | 20 +- .../examples/tracing-chrome/main.rs | 7 +- .../examples/tracing-otlp/main.rs | 12 +- src/hyperlight_host/examples/tracing/main.rs | 18 +- .../src/func/host_functions.rs | 16 +- src/hyperlight_host/src/lib.rs | 2 +- src/hyperlight_host/src/metrics/mod.rs | 10 +- src/hyperlight_host/src/sandbox/host_funcs.rs | 11 +- .../src/sandbox/initialized_multi_use.rs | 938 +++++++----------- .../src/sandbox/snapshot/file/mod.rs | 7 +- .../src/sandbox/snapshot/file_tests.rs | 165 ++- .../src/sandbox/uninitialized.rs | 26 +- src/hyperlight_host/tests/common/mod.rs | 117 +-- src/hyperlight_host/tests/integration_test.rs | 183 ++-- .../tests/sandbox_host_tests.rs | 56 +- .../tests/snapshot_goldens/checks.rs | 14 +- .../tests/snapshot_goldens/fixtures.rs | 31 +- 31 files changed, 781 insertions(+), 1266 deletions(-) diff --git a/README.md b/README.md index ade86b6d2d..f8942fd6de 100644 --- a/README.md +++ b/README.md @@ -21,17 +21,12 @@ Hyperlight lets you safely run untrusted code inside hypervisor-isolated micro V **Host** - create a sandbox, register a host function, and call into the guest: ```rust -// Create an uninitialized sandbox by giving it the path to a guest binary. -// Allocates memory but does not yet run a VM. -let mut sandbox = UninitializedSandbox::new(GuestBinary::FilePath(guest_path), None)?; - -// Register a host function that the guest can call. In a real app this -// might query a database, read a config, or call an external API. -// By default, guests can only print to the host. -sandbox.register("GetWeekday", || Ok("Monday".to_string()))?; - -// Initialize the sandbox. Starts the VM and runs guest setup code. -let mut sandbox: MultiUseSandbox = sandbox.evolve()?; +// Build a sandbox from a guest binary, registering a host function the guest +// can call. In a real app that function might query a database, read a config, +// or call an external API. By default, guests can only print to the host. +let mut sandbox = SandboxBuilder::new() + .host_function("GetWeekday", || Ok("Monday".to_string())) + .build_from_file(guest_path)?; // Call a function inside the VM let greeting: String = sandbox.call("SayHello", "World".to_string())?; diff --git a/docs/how-to-debug-a-hyperlight-guest.md b/docs/how-to-debug-a-hyperlight-guest.md index 4640b46bb2..2983053139 100644 --- a/docs/how-to-debug-a-hyperlight-guest.md +++ b/docs/how-to-debug-a-hyperlight-guest.md @@ -21,8 +21,8 @@ The Hyperlight `gdb` feature enables guest debugging to: Below is a list describing some cases of expected behavior from a gdb debug session of a guest binary running inside a Hyperlight sandbox. -- when the `gdb` feature is enabled and a SandboxConfiguration is provided a - debug port, the created sandbox will wait for a gdb client to connect on the +- when the `gdb` feature is enabled and the sandbox builder is given a debug + port, the created sandbox will wait for a gdb client to connect on the configured port - when the gdb client attaches, the guest vCPU is expected to be stopped at the entry point @@ -220,10 +220,11 @@ The name and location of the dump file will be printed to the console and logged **NOTE**: If the directory provided by `HYPERLIGHT_CORE_DUMP_DIR` does not exist, Hyperlight places the file in the temporary directory. **NOTE**: By enabling the `crashdump` feature, you instruct Hyperlight to create core dump files for all sandboxes when an unhandled crash occurs. -To selectively disable this feature for a specific sandbox, you can set the `guest_core_dump` field to `false` in the `SandboxConfiguration`. +To selectively disable this feature for a specific sandbox, call `guest_core_dump(false)` on the `SandboxBuilder`. ```rust - let mut cfg = SandboxConfiguration::default(); - cfg.set_guest_core_dump(false); // Disable core dump for this sandbox + let sandbox = SandboxBuilder::new() + .guest_core_dump(false) // Disable core dump for this sandbox + .build_from_file(guest_path)?; ``` ## Creating a dump on demand diff --git a/docs/msr.md b/docs/msr.md index c09ed5a805..3914e0ad6d 100644 --- a/docs/msr.md +++ b/docs/msr.md @@ -12,7 +12,7 @@ the supplied snapshot, regardless of prior execution in the sandbox. A snapshot saves the value of two groups of MSRs: -* The MSRs you list with `SandboxConfiguration::guest_msrs`. List the ones your +* The MSRs you list with `SandboxBuilder::guest_msrs`. List the ones your guest reads or writes. * A small fixed core the guest can change without a `WRMSR`, so Hyperlight always saves it: `KERNEL_GS_BASE` (via `SWAPGS`), `TSC`, and active SSP on @@ -58,7 +58,7 @@ set. Restore applies each captured value and scrubs the rest of the reset set to the destination baseline, so the destination configuration alone governs guest MSR access. -`SandboxConfiguration::guest_msrs` accepts at most 16 distinct indices. KVM +`SandboxBuilder::guest_msrs` accepts at most 16 distinct indices. KVM also supports at most 16 contiguous filter ranges. Each declared index must be resettable, host-readable, and host-writable. Write-only command MSRs such as `PRED_CMD` and `FLUSH_CMD` hold no resettable state and cannot be declared. diff --git a/fuzz/fuzz_targets/guest_call.rs b/fuzz/fuzz_targets/guest_call.rs index 86c30424c3..e1e37e2784 100644 --- a/fuzz/fuzz_targets/guest_call.rs +++ b/fuzz/fuzz_targets/guest_call.rs @@ -6,8 +6,7 @@ use std::sync::{Mutex, OnceLock}; use hyperlight_host::func::{ParameterValue, ReturnType}; -use hyperlight_host::sandbox::uninitialized::GuestBinary; -use hyperlight_host::{MultiUseSandbox, UninitializedSandbox}; +use hyperlight_host::{MultiUseSandbox, SandboxBuilder}; use hyperlight_testing::simple_guest_for_fuzzing_as_pathbuf; use libfuzzer_sys::fuzz_target; static SANDBOX: OnceLock> = OnceLock::new(); @@ -16,14 +15,9 @@ static SANDBOX: OnceLock> = OnceLock::new(); // For fuzzing efficiency, we create one Sandbox and reuse it for all fuzzing iterations. fuzz_target!( init: { - - let u_sbox = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_for_fuzzing_as_pathbuf()), - None, - ) - .unwrap(); - - let mu_sbox: MultiUseSandbox = u_sbox.evolve().unwrap(); + let mu_sbox = SandboxBuilder::new() + .build_from_file(simple_guest_for_fuzzing_as_pathbuf()) + .unwrap(); SANDBOX.set(Mutex::new(mu_sbox)).unwrap(); }, diff --git a/fuzz/fuzz_targets/guest_trace.rs b/fuzz/fuzz_targets/guest_trace.rs index 131ff2cac9..c39df9a486 100644 --- a/fuzz/fuzz_targets/guest_trace.rs +++ b/fuzz/fuzz_targets/guest_trace.rs @@ -9,9 +9,7 @@ compile_error!("feature `trace` must be enabled to correctly fuzz guest trace fu use std::sync::{Mutex, OnceLock}; use hyperlight_host::func::{ParameterValue, ReturnType, ReturnValue}; -use hyperlight_host::sandbox::SandboxConfiguration; -use hyperlight_host::sandbox::uninitialized::GuestBinary; -use hyperlight_host::{MultiUseSandbox, UninitializedSandbox}; +use hyperlight_host::{MultiUseSandbox, SandboxBuilder}; use hyperlight_testing::simple_guest_for_fuzzing_as_pathbuf; use libfuzzer_sys::arbitrary::Arbitrary; use libfuzzer_sys::{Corpus, fuzz_target}; @@ -55,14 +53,11 @@ impl<'a> Arbitrary<'a> for FuzzInput { // Any unexpected errors from the guest should be reported. fuzz_target!( init: { - let mut cfg = SandboxConfiguration::default(); // In local tests, 256 KiB seemed sufficient for deep recursion - cfg.set_scratch_size(256 * 1024); - let path = simple_guest_for_fuzzing_as_pathbuf(); - let u_sbox = - UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg)).unwrap(); - - let mu_sbox: MultiUseSandbox = u_sbox.evolve().unwrap(); + let mu_sbox = SandboxBuilder::new() + .scratch_size(256 * 1024) + .build_from_file(simple_guest_for_fuzzing_as_pathbuf()) + .unwrap(); SANDBOX.set(Mutex::new(mu_sbox)).unwrap(); }, diff --git a/fuzz/fuzz_targets/host_call.rs b/fuzz/fuzz_targets/host_call.rs index 88ae2393c5..82ecdce5b3 100644 --- a/fuzz/fuzz_targets/host_call.rs +++ b/fuzz/fuzz_targets/host_call.rs @@ -7,9 +7,7 @@ use std::sync::{Mutex, OnceLock}; use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; use hyperlight_host::func::{ParameterValue, ReturnType}; -use hyperlight_host::sandbox::SandboxConfiguration; -use hyperlight_host::sandbox::uninitialized::GuestBinary; -use hyperlight_host::{HyperlightError, MultiUseSandbox, UninitializedSandbox}; +use hyperlight_host::{HyperlightError, MultiUseSandbox, SandboxBuilder}; use hyperlight_testing::simple_guest_for_fuzzing_as_pathbuf; use libfuzzer_sys::fuzz_target; @@ -19,17 +17,12 @@ static SANDBOX: OnceLock> = OnceLock::new(); // For fuzzing efficiency, we create one Sandbox and reuse it for all fuzzing iterations. fuzz_target!( init: { - let mut cfg = SandboxConfiguration::default(); - cfg.set_output_data_size(64 * 1024); // 64 KB output buffer - cfg.set_input_data_size(64 * 1024); // 64 KB input buffer - cfg.set_scratch_size(512 * 1024); // large scratch region to contain those buffers, any data copies, etc. - let u_sbox = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_for_fuzzing_as_pathbuf()), - Some(cfg) - ) - .unwrap(); - - let mu_sbox: MultiUseSandbox = u_sbox.evolve().unwrap(); + let mu_sbox = SandboxBuilder::new() + .output_data_size(64 * 1024) // 64 KB output buffer + .input_data_size(64 * 1024) // 64 KB input buffer + .scratch_size(512 * 1024) // large scratch region to contain those buffers, any data copies, etc. + .build_from_file(simple_guest_for_fuzzing_as_pathbuf()) + .unwrap(); SANDBOX.set(Mutex::new(mu_sbox)).unwrap(); }, diff --git a/fuzz/fuzz_targets/host_print.rs b/fuzz/fuzz_targets/host_print.rs index 6b2132a377..89ccc1dfcf 100644 --- a/fuzz/fuzz_targets/host_print.rs +++ b/fuzz/fuzz_targets/host_print.rs @@ -2,8 +2,7 @@ use std::sync::{Mutex, OnceLock}; -use hyperlight_host::sandbox::uninitialized::GuestBinary; -use hyperlight_host::{MultiUseSandbox, UninitializedSandbox}; +use hyperlight_host::{MultiUseSandbox, SandboxBuilder}; use hyperlight_testing::simple_guest_for_fuzzing_as_pathbuf; use libfuzzer_sys::{Corpus, fuzz_target}; @@ -15,13 +14,9 @@ static SANDBOX: OnceLock> = OnceLock::new(); // For fuzzing efficiency, we create one Sandbox and reuse it for all fuzzing iterations. fuzz_target!( init: { - let u_sbox = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_for_fuzzing_as_pathbuf()), - None, - ) - .unwrap(); - - let mu_sbox: MultiUseSandbox = u_sbox.evolve().unwrap(); + let mu_sbox = SandboxBuilder::new() + .build_from_file(simple_guest_for_fuzzing_as_pathbuf()) + .unwrap(); SANDBOX.set(Mutex::new(mu_sbox)).unwrap(); }, diff --git a/src/hyperlight_host/benches/benchmarks.rs b/src/hyperlight_host/benches/benchmarks.rs index cb3d0efbb5..befe46fa37 100644 --- a/src/hyperlight_host/benches/benchmarks.rs +++ b/src/hyperlight_host/benches/benchmarks.rs @@ -10,9 +10,9 @@ use flatbuffers::FlatBufferBuilder; use hyperlight_common::flatbuffer_wrappers::function_call::{FunctionCall, FunctionCallType}; use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnType}; use hyperlight_common::flatbuffer_wrappers::util::estimate_flatbuffer_capacity; -use hyperlight_host::GuestBinary; +use hyperlight_host::SandboxBuilder; use hyperlight_host::mem::shared_mem::ExclusiveSharedMemory; -use hyperlight_host::sandbox::{MultiUseSandbox, SandboxConfiguration, UninitializedSandbox}; +use hyperlight_host::sandbox::MultiUseSandbox; use hyperlight_testing::sandbox_sizes::{LARGE_HEAP_SIZE, MEDIUM_HEAP_SIZE, SMALL_HEAP_SIZE}; use hyperlight_testing::{c_simple_guest_as_pathbuf, simple_guest_as_pathbuf}; @@ -31,28 +31,14 @@ enum SandboxSize { } impl SandboxSize { - /// Returns the configuration for this sandbox size. - /// Returns None for Default to use hyperlight's default configuration. - fn config(&self) -> Option { + /// Returns a builder configured for this sandbox size. + fn builder(&self) -> SandboxBuilder { + let builder = SandboxBuilder::new(); match self { - Self::Default => None, - Self::Small => { - let mut cfg = SandboxConfiguration::default(); - cfg.set_heap_size(SMALL_HEAP_SIZE); - Some(cfg) - } - Self::Medium => { - let mut cfg = SandboxConfiguration::default(); - cfg.set_heap_size(MEDIUM_HEAP_SIZE); - cfg.set_scratch_size(0x50000); - Some(cfg) - } - Self::Large => { - let mut cfg = SandboxConfiguration::default(); - cfg.set_heap_size(LARGE_HEAP_SIZE); - cfg.set_scratch_size(0x100000); - Some(cfg) - } + Self::Default => builder, + Self::Small => builder.heap_size(SMALL_HEAP_SIZE), + Self::Medium => builder.heap_size(MEDIUM_HEAP_SIZE).scratch_size(0x50000), + Self::Large => builder.heap_size(LARGE_HEAP_SIZE).scratch_size(0x100000), } } @@ -72,32 +58,16 @@ impl SandboxSize { } } -fn create_uninit_sandbox_with_size(size: SandboxSize) -> UninitializedSandbox { - let path = simple_guest_as_pathbuf(); - UninitializedSandbox::new(GuestBinary::FilePath(path), size.config()).unwrap() -} - fn create_multiuse_sandbox_with_size(size: SandboxSize) -> MultiUseSandbox { - create_uninit_sandbox_with_size(size).evolve().unwrap() + size.builder() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap() } // ============================================================================ // Benchmark Category: Sandbox Lifecycle // ============================================================================ -fn bench_create_uninitialized(b: &mut criterion::Bencher, size: SandboxSize) { - // Ideally wanted to use b.iter_with_large_drop, but runs out of memory on windows runners: "The paging file is too small for this operation to complete." - b.iter_batched( - || (), - |_| create_uninit_sandbox_with_size(size), - criterion::BatchSize::PerIteration, - ); -} - -fn bench_create_uninitialized_and_drop(b: &mut criterion::Bencher, size: SandboxSize) { - b.iter(|| create_uninit_sandbox_with_size(size)); -} - fn bench_create_initialized(b: &mut criterion::Bencher, size: SandboxSize) { // Ideally wanted to use b.iter_with_large_drop, but runs out of memory on windows runners: "The paging file is too small for this operation to complete." b.iter_batched( @@ -114,19 +84,6 @@ fn bench_create_initialized_and_drop(b: &mut criterion::Bencher, size: SandboxSi fn sandbox_lifecycle_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("sandboxes"); - for size in SandboxSize::all() { - group.bench_function(format!("create_uninitialized/{}", size.name()), |b| { - bench_create_uninitialized(b, size) - }); - } - - for size in SandboxSize::all() { - group.bench_function( - format!("create_uninitialized_and_drop/{}", size.name()), - |b| bench_create_uninitialized_and_drop(b, size), - ); - } - for size in SandboxSize::all() { group.bench_function(format!("create_initialized/{}", size.name()), |b| { bench_create_initialized(b, size) @@ -172,14 +129,12 @@ fn bench_guest_call_with_restore(b: &mut criterion::Bencher, size: SandboxSize) } fn bench_guest_call_with_host_function(b: &mut criterion::Bencher, size: SandboxSize) { - let mut uninitialized_sandbox = create_uninit_sandbox_with_size(size); - - uninitialized_sandbox - .register("HostAdd", |a: i32, b: i32| Ok(a + b)) + let mut multiuse_sandbox = size + .builder() + .host_function("HostAdd", |a: i32, b: i32| Ok(a + b)) + .build_from_file(simple_guest_as_pathbuf()) .unwrap(); - let mut multiuse_sandbox: MultiUseSandbox = uninitialized_sandbox.evolve().unwrap(); - b.iter(|| { multiuse_sandbox .call::("Add", (1_i32, 41_i32)) @@ -397,17 +352,14 @@ fn guest_call_benchmark_large_param(c: &mut Criterion) { let large_vec = vec![0u8; SIZE]; let large_string = String::from_utf8(large_vec.clone()).unwrap(); - let mut config = SandboxConfiguration::default(); - config.set_input_data_size(2 * SIZE + (1024 * 1024)); // 2 * SIZE + 1 MB, to allow 1MB for the rest of the serialized function call - config.set_heap_size(SIZE as u64 * 15); - config.set_scratch_size(6 * SIZE + 4 * (1024 * 1024)); // Big enough for the IO data regions and enough of the heap to be used - - let sandbox = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_pathbuf()), - Some(config), - ) - .unwrap(); - let mut sandbox = sandbox.evolve().unwrap(); + let mut sandbox = SandboxBuilder::new() + // 2 * SIZE + 1 MB, to allow 1MB for the rest of the serialized function call + .input_data_size(2 * SIZE + (1024 * 1024)) + .heap_size(SIZE as u64 * 15) + // Big enough for the IO data regions and enough of the heap to be used + .scratch_size(6 * SIZE + 4 * (1024 * 1024)) + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); b.iter_with_setup( || (large_vec.clone(), large_string.clone()), @@ -482,12 +434,9 @@ fn sample_workloads_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("sample_workloads"); fn bench_24k_in_8k_out(b: &mut criterion::Bencher, guest_path: std::path::PathBuf) { - let mut cfg = SandboxConfiguration::default(); - cfg.set_input_data_size(25 * 1024); - - let mut sandbox = UninitializedSandbox::new(GuestBinary::FilePath(guest_path), Some(cfg)) - .unwrap() - .evolve() + let mut sandbox = SandboxBuilder::new() + .input_data_size(25 * 1024) + .build_from_file(guest_path) .unwrap(); b.iter_with_setup( diff --git a/src/hyperlight_host/examples/crashdump/main.rs b/src/hyperlight_host/examples/crashdump/main.rs index dfd1b4fa41..fbd7eddcca 100644 --- a/src/hyperlight_host/examples/crashdump/main.rs +++ b/src/hyperlight_host/examples/crashdump/main.rs @@ -30,7 +30,7 @@ //! //! 3. **Disabling crash dumps per sandbox** — You can opt out of crash dump //! generation for individual sandboxes via -//! [`SandboxConfiguration::set_guest_core_dump`]. +//! [`SandboxBuilder::guest_core_dump`]. //! //! 4. **On-demand crash dump from a debugger** — The `generate_crashdump()` //! method is available for use from gdb while the guest is mid-execution. @@ -64,8 +64,7 @@ use std::path::Path; #[cfg(all(crashdump, target_os = "linux"))] use hyperlight_host::HyperlightError; -use hyperlight_host::sandbox::SandboxConfiguration; -use hyperlight_host::{GuestBinary, MultiUseSandbox, UninitializedSandbox}; +use hyperlight_host::SandboxBuilder; fn main() -> hyperlight_host::Result<()> { // Only enable logging if the user explicitly sets RUST_LOG; keep @@ -127,12 +126,7 @@ fn main() -> hyperlight_host::Result<()> { /// 4. The crash dump is written automatically (no explicit call needed) #[cfg(all(crashdump, target_os = "linux"))] fn guest_crash_auto_dump(guest_path: &Path) -> hyperlight_host::Result<()> { - let cfg = SandboxConfiguration::default(); - - let uninitialized_sandbox = - UninitializedSandbox::new(GuestBinary::FilePath(guest_path.to_path_buf()), Some(cfg))?; - - let mut sandbox: MultiUseSandbox = uninitialized_sandbox.evolve()?; + let mut sandbox = SandboxBuilder::new().build_from_file(guest_path)?; // Map a file as read-only into the guest at a known address. let mapping_file = create_mapping_file(); @@ -192,12 +186,7 @@ fn create_mapping_file() -> std::path::PathBuf { /// fault), the automatic crash dump code in the VM run loop is not reached. /// To get a crash dump in this case, call `generate_crashdump()` explicitly. fn guest_crash_with_on_demand_dump(guest_path: &Path) -> hyperlight_host::Result<()> { - let cfg = SandboxConfiguration::default(); - - let uninitialized_sandbox = - UninitializedSandbox::new(GuestBinary::FilePath(guest_path.to_path_buf()), Some(cfg))?; - - let mut sandbox: MultiUseSandbox = uninitialized_sandbox.evolve()?; + let mut sandbox = SandboxBuilder::new().build_from_file(guest_path)?; // This call triggers a ud2 instruction in the guest. The guest's IDT // catches the #UD exception and reports it back to the host as a @@ -233,14 +222,11 @@ fn guest_crash_with_on_demand_dump(guest_path: &Path) -> hyperlight_host::Result /// writing core dump files. #[cfg(all(crashdump, target_os = "linux"))] fn guest_crash_with_dump_disabled(guest_path: &Path) -> hyperlight_host::Result<()> { - let mut cfg = SandboxConfiguration::default(); - cfg.set_guest_core_dump(false); println!("Core dump disabled for this sandbox."); - let uninitialized_sandbox = - UninitializedSandbox::new(GuestBinary::FilePath(guest_path.to_path_buf()), Some(cfg))?; - - let mut sandbox: MultiUseSandbox = uninitialized_sandbox.evolve()?; + let mut sandbox = SandboxBuilder::new() + .guest_core_dump(false) + .build_from_file(guest_path)?; let mapping_file = create_mapping_file(); let guest_base: u64 = 0x200000000; @@ -328,8 +314,7 @@ mod tests { use std::path::{Path, PathBuf}; use std::process::Command; - use hyperlight_host::sandbox::SandboxConfiguration; - use hyperlight_host::{GuestBinary, HostFunctions, MultiUseSandbox, UninitializedSandbox}; + use hyperlight_host::SandboxBuilder; use serial_test::serial; #[cfg(not(windows))] @@ -375,10 +360,7 @@ mod tests { // Create sandbox with default config (crashdump enabled) let guest_path = hyperlight_testing::simple_guest_as_pathbuf(); - let cfg = SandboxConfiguration::default(); - let u_sbox = - UninitializedSandbox::new(GuestBinary::FilePath(guest_path), Some(cfg)).unwrap(); - let mut sbox: MultiUseSandbox = u_sbox.evolve().unwrap(); + let mut sbox = SandboxBuilder::new().build_from_file(guest_path).unwrap(); // Map an additional test file into the guest at a known address. // The core dump already includes snapshot and scratch regions @@ -447,18 +429,17 @@ mod tests { /// sandboxes resolve symbols the same way as directly-evolved ones. fn generate_crashdump_from_snapshot(dump_dir: &Path) -> PathBuf { let guest_path = hyperlight_testing::simple_guest_as_pathbuf(); - let mut cfg = SandboxConfiguration::default(); - cfg.set_guest_core_dump(true); - let u_sbox = - UninitializedSandbox::new(GuestBinary::FilePath(guest_path), Some(cfg)).unwrap(); - let mut sbox: MultiUseSandbox = u_sbox.evolve().unwrap(); + let mut sbox = SandboxBuilder::new() + .guest_core_dump(true) + .build_from_file(guest_path) + .unwrap(); let snapshot = sbox.snapshot().expect("snapshot"); - let mut cfg2 = SandboxConfiguration::default(); - cfg2.set_guest_core_dump(true); - let mut sbox2 = - MultiUseSandbox::from_snapshot(snapshot, HostFunctions::default(), Some(cfg2)).unwrap(); + let mut sbox2 = SandboxBuilder::new() + .guest_core_dump(true) + .build_from_snapshot(snapshot) + .unwrap(); let result = sbox2.call::<()>("TriggerException", ()); assert!(result.is_err(), "TriggerException should return an error"); diff --git a/src/hyperlight_host/examples/func_ctx/main.rs b/src/hyperlight_host/examples/func_ctx/main.rs index 415475d998..0199b47459 100644 --- a/src/hyperlight_host/examples/func_ctx/main.rs +++ b/src/hyperlight_host/examples/func_ctx/main.rs @@ -1,18 +1,14 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2025 The Hyperlight Authors. -use hyperlight_host::GuestBinary; -use hyperlight_host::sandbox::UninitializedSandbox; +use hyperlight_host::SandboxBuilder; use hyperlight_testing::simple_guest_as_pathbuf; fn main() { // create a new `MultiUseSandbox` configured to run the `simpleguest.exe` // test guest binary let path = simple_guest_as_pathbuf(); - let mut sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None) - .unwrap() - .evolve() - .unwrap(); + let mut sbox = SandboxBuilder::new().build_from_file(path).unwrap(); // Do several calls against a sandbox running the `simpleguest.exe` binary, // and print their results diff --git a/src/hyperlight_host/examples/guest-debugging/main.rs b/src/hyperlight_host/examples/guest-debugging/main.rs index dafc162481..847e7186a6 100644 --- a/src/hyperlight_host/examples/guest-debugging/main.rs +++ b/src/hyperlight_host/examples/guest-debugging/main.rs @@ -2,56 +2,36 @@ // Copyright 2025 The Hyperlight Authors. use std::thread; -use hyperlight_host::sandbox::SandboxConfiguration; +use hyperlight_host::SandboxBuilder; #[cfg(gdb)] use hyperlight_host::sandbox::config::DebugInfo; -use hyperlight_host::{MultiUseSandbox, UninitializedSandbox}; -/// Build a sandbox configuration that enables GDB debugging when the `gdb` feature is enabled. -fn get_sandbox_cfg() -> Option { - #[cfg(gdb)] - { - let mut cfg = SandboxConfiguration::default(); - let debug_info = DebugInfo { port: 8080 }; - cfg.set_guest_debug_info(debug_info); +/// Build a sandbox builder that enables GDB debugging when the `gdb` feature is enabled. +fn debuggable_builder() -> SandboxBuilder { + let builder = SandboxBuilder::new(); - Some(cfg) - } + #[cfg(gdb)] + let builder = builder.guest_debug_info(DebugInfo { port: 8080 }); - #[cfg(not(gdb))] - None + builder } fn main() -> hyperlight_host::Result<()> { - let cfg = get_sandbox_cfg(); - - // Create an uninitialized sandbox with a guest binary and debug enabled - let mut uninitialized_sandbox_dbg = UninitializedSandbox::new( - hyperlight_host::GuestBinary::FilePath(hyperlight_testing::simple_guest_as_pathbuf()), - cfg, // sandbox configuration - )?; - - // Create an uninitialized sandbox with a guest binary - let mut uninitialized_sandbox = UninitializedSandbox::new( - hyperlight_host::GuestBinary::FilePath(hyperlight_testing::simple_guest_as_pathbuf()), - None, // sandbox configuration - )?; - - // Register a host functions - uninitialized_sandbox_dbg.register("Sleep5Secs", || { - thread::sleep(std::time::Duration::from_secs(5)); - Ok(()) - })?; - // Register a host functions - uninitialized_sandbox.register("Sleep5Secs", || { + let sleep_5_secs = || { thread::sleep(std::time::Duration::from_secs(5)); Ok(()) - })?; - // Note: This function is unused, it's just here for demonstration purposes + }; + // Note: the host function is unused, it's just here for demonstration purposes - // Initialize sandboxes to be able to call host functions - let mut multi_use_sandbox_dbg: MultiUseSandbox = uninitialized_sandbox_dbg.evolve()?; - let mut multi_use_sandbox: MultiUseSandbox = uninitialized_sandbox.evolve()?; + // Build a sandbox with a guest binary and debug enabled + let mut multi_use_sandbox_dbg = debuggable_builder() + .host_function("Sleep5Secs", sleep_5_secs) + .build_from_file(hyperlight_testing::simple_guest_as_pathbuf())?; + + // Build a sandbox with a guest binary + let mut multi_use_sandbox = SandboxBuilder::new() + .host_function("Sleep5Secs", sleep_5_secs) + .build_from_file(hyperlight_testing::simple_guest_as_pathbuf())?; // Call guest function multi_use_sandbox_dbg @@ -353,20 +333,14 @@ mod tests { #[test] #[serial] fn test_gdb_from_snapshot() { - use hyperlight_host::HostFunctions; - const PORT: u16 = 8081; let (out_file_path, cmd_file_path, manifest_dir) = gdb_test_paths("gdb-from-snapshot"); // Build a sandbox the normal way and snapshot it in-memory. - let mut producer: MultiUseSandbox = UninitializedSandbox::new( - hyperlight_host::GuestBinary::FilePath(hyperlight_testing::simple_guest_as_pathbuf()), - None, - ) - .unwrap() - .evolve() - .unwrap(); + let mut producer = SandboxBuilder::new() + .build_from_file(hyperlight_testing::simple_guest_as_pathbuf()) + .unwrap(); let snap = producer.snapshot().unwrap(); // Order matters. The gdb stub event loop must enter (i.e. @@ -379,11 +353,9 @@ mod tests { // here before the client is launched below. let snap_thread = snap.clone(); let sandbox_thread = thread::spawn(move || -> Result<()> { - let mut cfg = SandboxConfiguration::default(); - cfg.set_guest_debug_info(DebugInfo { port: PORT }); - - let mut sbox = - MultiUseSandbox::from_snapshot(snap_thread, HostFunctions::default(), Some(cfg))?; + let mut sbox = SandboxBuilder::new() + .guest_debug_info(DebugInfo { port: PORT }) + .build_from_snapshot(snap_thread)?; sbox.call::( "PrintOutput", "Hello from a from_snapshot sandbox\n".to_string(), diff --git a/src/hyperlight_host/examples/hello-world/main.rs b/src/hyperlight_host/examples/hello-world/main.rs index 12bd5360d3..2b35664009 100644 --- a/src/hyperlight_host/examples/hello-world/main.rs +++ b/src/hyperlight_host/examples/hello-world/main.rs @@ -2,28 +2,21 @@ // Copyright 2025 The Hyperlight Authors. use std::thread; -use hyperlight_host::{MultiUseSandbox, UninitializedSandbox}; +use hyperlight_host::SandboxBuilder; fn main() -> hyperlight_host::Result<()> { - // Create an uninitialized sandbox with a guest binary - let mut uninitialized_sandbox = UninitializedSandbox::new( - hyperlight_host::GuestBinary::FilePath(hyperlight_testing::simple_guest_as_pathbuf()), - None, // default configuration - )?; - - // Register a host functions - uninitialized_sandbox.register("Sleep5Secs", || { - thread::sleep(std::time::Duration::from_secs(5)); - Ok(()) - })?; - // Note: This function is unused, it's just here for demonstration purposes - - // Initialize sandbox to be able to call host functions - let mut multi_use_sandbox: MultiUseSandbox = uninitialized_sandbox.evolve()?; + // Build a sandbox running a guest binary, with a host function registered. + // Note: the host function is unused, it's just here for demonstration purposes + let mut sandbox = SandboxBuilder::new() + .host_function("Sleep5Secs", || { + thread::sleep(std::time::Duration::from_secs(5)); + Ok(()) + }) + .build_from_file(hyperlight_testing::simple_guest_as_pathbuf())?; // Call guest function let message = "Hello, World! I am executing inside of a VM :)\n".to_string(); - multi_use_sandbox + sandbox .call::( "PrintOutput", // function must be defined in the guest binary message, diff --git a/src/hyperlight_host/examples/logging/main.rs b/src/hyperlight_host/examples/logging/main.rs index d512aa57d8..c6ca53dbb8 100644 --- a/src/hyperlight_host/examples/logging/main.rs +++ b/src/hyperlight_host/examples/logging/main.rs @@ -4,8 +4,7 @@ extern crate hyperlight_host; use std::sync::{Arc, Barrier}; -use hyperlight_host::sandbox::uninitialized::UninitializedSandbox; -use hyperlight_host::{GuestBinary, Result}; +use hyperlight_host::{Result, SandboxBuilder}; use hyperlight_testing::simple_guest_as_pathbuf; fn fn_writer(_msg: String) -> Result { @@ -26,11 +25,9 @@ fn main() -> Result<()> { let path = hyperlight_guest_path.clone(); let res: Result<()> = { // Create a new sandbox. - let mut usandbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None)?; - usandbox.register_print(fn_writer)?; - - // Initialize the sandbox. - let mut multiuse_sandbox = usandbox.evolve()?; + let mut multiuse_sandbox = SandboxBuilder::new() + .host_print(fn_writer) + .build_from_file(path)?; // Call a guest function 5 times to generate some log entries. for _ in 0..5 { @@ -56,11 +53,8 @@ fn main() -> Result<()> { } // Create a new sandbox. - let usandbox = - UninitializedSandbox::new(GuestBinary::FilePath(hyperlight_guest_path.clone()), None)?; - - // Initialize the sandbox. - let mut multiuse_sandbox = usandbox.evolve()?; + let mut multiuse_sandbox = + SandboxBuilder::new().build_from_file(hyperlight_guest_path.clone())?; let interrupt_handle = multiuse_sandbox.interrupt_handle(); let barrier = Arc::new(Barrier::new(2)); let barrier2 = barrier.clone(); diff --git a/src/hyperlight_host/examples/map-file-cow-test/main.rs b/src/hyperlight_host/examples/map-file-cow-test/main.rs index 3cba2b7b0a..9f4a4dd46c 100644 --- a/src/hyperlight_host/examples/map-file-cow-test/main.rs +++ b/src/hyperlight_host/examples/map-file-cow-test/main.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2025 The Hyperlight Authors. -// Test that map_file_cow works end-to-end: UninitializedSandbox::new → -// map_file_cow → evolve → guest function call. Exercises the cross-process +// Test that mapping a file copy-on-write works end-to-end: build a sandbox with +// a mapped file, then call a guest function. Exercises the cross-process // section mapping via MapViewOfFileNuma2 on Windows (the surrogate process // must be able to map the file-backed section). // @@ -17,30 +17,20 @@ use std::path::Path; -use hyperlight_host::sandbox::SandboxConfiguration; -use hyperlight_host::{MultiUseSandbox, UninitializedSandbox}; +use hyperlight_host::SandboxBuilder; fn run_once(test_file: &Path, label: &str) -> hyperlight_host::Result<()> { - let mut config = SandboxConfiguration::default(); - config.set_heap_size(4 * 1024 * 1024); - config.set_scratch_size(64 * 1024 * 1024); - - let mut usbox = UninitializedSandbox::new( - hyperlight_host::GuestBinary::FilePath(hyperlight_testing::simple_guest_as_pathbuf()), - Some(config), - )?; - eprintln!("[{label}] UninitializedSandbox::new OK"); - - usbox.map_file_cow(test_file, 0xC000_0000)?; + let mut sandbox = SandboxBuilder::new() + .heap_size(4 * 1024 * 1024) + .scratch_size(64 * 1024 * 1024) + .mapped_file_cow(test_file, 0xC000_0000) + .build_from_file(hyperlight_testing::simple_guest_as_pathbuf())?; eprintln!( - "[{label}] map_file_cow OK ({} bytes)", + "[{label}] sandbox built with a {} byte file mapped", std::fs::metadata(test_file)?.len() ); - let mut mu: MultiUseSandbox = usbox.evolve()?; - eprintln!("[{label}] evolve OK"); - - let result: String = mu.call("Echo", format!("{label}: map_file_cow works!"))?; + let result: String = sandbox.call("Echo", format!("{label}: mapped_file_cow works!"))?; eprintln!("[{label}] guest returned: {result}"); Ok(()) } diff --git a/src/hyperlight_host/examples/metrics/main.rs b/src/hyperlight_host/examples/metrics/main.rs index 9ffa261f76..0d1480645c 100644 --- a/src/hyperlight_host/examples/metrics/main.rs +++ b/src/hyperlight_host/examples/metrics/main.rs @@ -4,8 +4,7 @@ extern crate hyperlight_host; use std::sync::{Arc, Barrier}; use std::thread::{JoinHandle, spawn}; -use hyperlight_host::sandbox::uninitialized::UninitializedSandbox; -use hyperlight_host::{GuestBinary, Result}; +use hyperlight_host::{Result, SandboxBuilder}; use hyperlight_testing::simple_guest_as_pathbuf; // Run this rust example with the flag --features "function_call_metrics" to enable more metrics to be emitted @@ -37,11 +36,9 @@ fn do_hyperlight_stuff() { let path = hyperlight_guest_path.clone(); let handle = spawn(move || -> Result<()> { // Create a new sandbox. - let mut usandbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None)?; - usandbox.register_print(fn_writer)?; - - // Initialize the sandbox. - let mut multiuse_sandbox = usandbox.evolve().expect("Failed to evolve sandbox"); + let mut multiuse_sandbox = SandboxBuilder::new() + .host_print(fn_writer) + .build_from_file(path)?; // Call a guest function 5 times to generate some metrics. for _ in 0..5 { @@ -67,12 +64,9 @@ fn do_hyperlight_stuff() { } // Create a new sandbox. - let usandbox = - UninitializedSandbox::new(GuestBinary::FilePath(hyperlight_guest_path.clone()), None) - .expect("Failed to create UninitializedSandbox"); - - // Initialize the sandbox. - let mut multiuse_sandbox = usandbox.evolve().expect("Failed to evolve sandbox"); + let mut multiuse_sandbox = SandboxBuilder::new() + .build_from_file(hyperlight_guest_path.clone()) + .expect("Failed to build sandbox"); let interrupt_handle = multiuse_sandbox.interrupt_handle(); const NUM_CALLS: i32 = 5; diff --git a/src/hyperlight_host/examples/tracing-chrome/main.rs b/src/hyperlight_host/examples/tracing-chrome/main.rs index f911f72e8a..7f4183d8d4 100644 --- a/src/hyperlight_host/examples/tracing-chrome/main.rs +++ b/src/hyperlight_host/examples/tracing-chrome/main.rs @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2025 The Hyperlight Authors. -use hyperlight_host::sandbox::uninitialized::UninitializedSandbox; -use hyperlight_host::{GuestBinary, Result}; +use hyperlight_host::{Result, SandboxBuilder}; use hyperlight_testing::simple_guest_as_pathbuf; use tracing_chrome::ChromeLayerBuilder; use tracing_subscriber::prelude::*; @@ -15,9 +14,7 @@ fn main() -> Result<()> { let simple_guest_path = simple_guest_as_pathbuf(); // Create a new sandbox. - let usandbox = UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_path), None)?; - - let mut sbox = usandbox.evolve().unwrap(); + let mut sbox = SandboxBuilder::new().build_from_file(simple_guest_path)?; // do the function call let current_time = std::time::Instant::now(); diff --git a/src/hyperlight_host/examples/tracing-otlp/main.rs b/src/hyperlight_host/examples/tracing-otlp/main.rs index 8bdeee9636..d8f1e61252 100644 --- a/src/hyperlight_host/examples/tracing-otlp/main.rs +++ b/src/hyperlight_host/examples/tracing-otlp/main.rs @@ -11,8 +11,7 @@ use std::io::stdin; use std::sync::{Arc, Barrier, Mutex}; use std::thread::{JoinHandle, spawn}; -use hyperlight_host::sandbox::uninitialized::UninitializedSandbox; -use hyperlight_host::{GuestBinary, Result as HyperlightResult}; +use hyperlight_host::{Result as HyperlightResult, SandboxBuilder}; use hyperlight_testing::simple_guest_as_pathbuf; use opentelemetry::trace::TracerProvider; use opentelemetry::{KeyValue, global}; @@ -110,12 +109,9 @@ fn run_example(wait_input: bool) -> HyperlightResult<()> { let _entered = span.enter(); // Create a new sandbox. - let mut usandbox = - UninitializedSandbox::new(GuestBinary::FilePath(path.clone()), None)?; - usandbox.register_print(fn_writer)?; - - // Initialize the sandbox. - let mut multiuse_sandbox = usandbox.evolve()?; + let mut multiuse_sandbox = SandboxBuilder::new() + .host_print(fn_writer) + .build_from_file(path.clone())?; // Call a guest function 5 times to generate some log entries. for _ in 0..5 { diff --git a/src/hyperlight_host/examples/tracing/main.rs b/src/hyperlight_host/examples/tracing/main.rs index 9c1200c538..dfd2ba5e12 100644 --- a/src/hyperlight_host/examples/tracing/main.rs +++ b/src/hyperlight_host/examples/tracing/main.rs @@ -5,8 +5,7 @@ extern crate hyperlight_host; use std::sync::{Arc, Barrier}; use std::thread::{JoinHandle, spawn}; -use hyperlight_host::sandbox::uninitialized::UninitializedSandbox; -use hyperlight_host::{GuestBinary, Result}; +use hyperlight_host::{Result, SandboxBuilder}; use hyperlight_testing::simple_guest_as_pathbuf; use tracing_forest::ForestLayer; use tracing_subscriber::layer::SubscriberExt; @@ -54,11 +53,9 @@ fn run_example() -> Result<()> { let _entered = span.enter(); // Create a new sandbox. - let mut usandbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None)?; - usandbox.register_print(fn_writer)?; - - // Initialize the sandbox. - let mut multiuse_sandbox = usandbox.evolve()?; + let mut multiuse_sandbox = SandboxBuilder::new() + .host_print(fn_writer) + .build_from_file(path)?; // Call a guest function 5 times to generate some log entries. for _ in 0..5 { @@ -83,11 +80,8 @@ fn run_example() -> Result<()> { } // Create a new sandbox. - let usandbox = - UninitializedSandbox::new(GuestBinary::FilePath(hyperlight_guest_path.clone()), None)?; - - // Initialize the sandbox. - let mut multiuse_sandbox = usandbox.evolve()?; + let mut multiuse_sandbox = + SandboxBuilder::new().build_from_file(hyperlight_guest_path.clone())?; let interrupt_handle = multiuse_sandbox.interrupt_handle(); // Call a function that gets cancelled by the host function 5 times to generate some log entries. diff --git a/src/hyperlight_host/src/func/host_functions.rs b/src/hyperlight_host/src/func/host_functions.rs index 93b8414cd9..9ead200d3e 100644 --- a/src/hyperlight_host/src/func/host_functions.rs +++ b/src/hyperlight_host/src/func/host_functions.rs @@ -47,20 +47,20 @@ impl Registerable for UninitializedSandbox { /// Allow registering host functions on an already-evolved /// [`crate::MultiUseSandbox`]. /// -/// The primary entry point for host-function registration is the -/// `UninitializedSandbox` impl above — that's the lifecycle phase -/// where the guest hasn't yet been allowed to issue host calls. +/// The primary entry point for host-function registration is +/// [`crate::SandboxBuilder::host_function`] — that's the lifecycle +/// phase where the guest hasn't yet been allowed to issue host calls. /// There are, however, cases where a `MultiUseSandbox` is obtained -/// without traversing the `Uninitialized → evolve()` path: +/// without going through the builder: /// /// - Sandboxes loaded from a persisted snapshot. /// - Any future API that yields a `MultiUseSandbox` directly. /// -/// In those cases the caller never had a chance to call -/// `register_host_function` on an `UninitializedSandbox`, so we -/// expose the same trait implementation here for late registration. +/// In those cases the caller never had a chance to register up front, +/// so we expose the same trait implementation here for late +/// registration. /// The guest's host-function dispatcher resolves by name at call -/// time, so inserting into the registry after `evolve()` is +/// time, so inserting into the registry after the sandbox is built is /// semantically safe as long as the first host-function invocation /// happens after registration completes. impl Registerable for crate::MultiUseSandbox { diff --git a/src/hyperlight_host/src/lib.rs b/src/hyperlight_host/src/lib.rs index 59b88dfb3c..28bbf94728 100644 --- a/src/hyperlight_host/src/lib.rs +++ b/src/hyperlight_host/src/lib.rs @@ -8,7 +8,7 @@ //! The runtime manages sandbox creation, guest function calls, memory isolation, //! and host-guest communication. //! -//! The primary entry points are [`UninitializedSandbox`] for initial setup and +//! The primary entry point is [`SandboxBuilder`], which produces a //! [`MultiUseSandbox`] for executing guest functions. //! //! ## Guest Requirements diff --git a/src/hyperlight_host/src/metrics/mod.rs b/src/hyperlight_host/src/metrics/mod.rs index 99654e6d60..015e159e5e 100644 --- a/src/hyperlight_host/src/metrics/mod.rs +++ b/src/hyperlight_host/src/metrics/mod.rs @@ -86,18 +86,16 @@ mod tests { use metrics_util::CompositeKey; use super::*; - use crate::{GuestBinary, UninitializedSandbox}; + use crate::SandboxBuilder; #[test] fn test_metrics_are_emitted() { let recorder = metrics_util::debugging::DebuggingRecorder::new(); let snapshotter = recorder.snapshotter(); let snapshot = with_local_recorder(&recorder, || { - let uninit = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap(); - - let mut multi = uninit.evolve().unwrap(); + let mut multi = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let interrupt_handle = multi.interrupt_handle(); // interrupt the guest function call to "Spin" after 1 second diff --git a/src/hyperlight_host/src/sandbox/host_funcs.rs b/src/hyperlight_host/src/sandbox/host_funcs.rs index 79c0f79905..c6704c078d 100644 --- a/src/hyperlight_host/src/sandbox/host_funcs.rs +++ b/src/hyperlight_host/src/sandbox/host_funcs.rs @@ -27,13 +27,12 @@ pub struct FunctionRegistry { /// expose host-side functionality to the guest. /// /// Use [`HostFunctions::default`] to start with the standard -/// `HostPrint` function pre-registered (matches the registry that the -/// regular `UninitializedSandbox` → `evolve()` path constructs), or +/// `HostPrint` function pre-registered (matching the registry a +/// [`crate::SandboxBuilder`] starts with), or /// [`HostFunctions::empty`] to start with an empty registry. /// /// Add additional host functions via the -/// [`crate::func::Registerable`] trait, just as you would on an -/// `UninitializedSandbox`. +/// [`crate::func::Registerable`] trait. /// /// ```no_run /// # use hyperlight_host::{HostFunctions, Result}; @@ -87,9 +86,9 @@ impl Default for HostFunctions { /// stdout in green). /// /// This matches the default registry installed by - /// `UninitializedSandbox::new()`, so a snapshot taken from a + /// `SandboxBuilder::new()`, so a snapshot taken from a /// regular sandbox can be loaded with - /// `MultiUseSandbox::from_snapshot(snap, HostFunctions::default(), None)` + /// `SandboxBuilder::new().build_from_snapshot(snap)` /// without registering anything else. /// /// Use [`HostFunctions::empty`] for an empty registry. diff --git a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs index 7d2de68f38..8613546dc1 100644 --- a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs +++ b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs @@ -158,8 +158,7 @@ impl MultiUseSandbox { } /// Create a `MultiUseSandbox` directly from a [`Snapshot`], - /// bypassing [`UninitializedSandbox`](crate::UninitializedSandbox) - /// and [`evolve()`](crate::UninitializedSandbox::evolve). + /// bypassing guest binary loading and initialization. /// /// This is useful for fast sandbox creation when a snapshot of /// an already-initialized guest is available, either saved to disk @@ -188,13 +187,10 @@ impl MultiUseSandbox { /// /// ```no_run /// # use std::sync::Arc; - /// # use hyperlight_host::{HostFunctions, MultiUseSandbox, UninitializedSandbox, GuestBinary}; + /// # use hyperlight_host::{HostFunctions, MultiUseSandbox, SandboxBuilder}; /// # fn example() -> Result<(), Box> { /// // Create and initialize a sandbox the normal way - /// let mut sandbox: MultiUseSandbox = UninitializedSandbox::new( - /// GuestBinary::FilePath("guest.bin".into()), - /// None, - /// )?.evolve()?; + /// let mut sandbox = SandboxBuilder::new().build_from_file("guest.bin")?; /// /// // Capture a snapshot of the initialized state /// let snapshot = sandbox.snapshot()?; @@ -344,7 +340,7 @@ impl MultiUseSandbox { /// /// On x86_64, the snapshot saves a small core of essential CPU state plus /// each MSR declared with - /// [`SandboxConfiguration::guest_msrs`](crate::sandbox::SandboxConfiguration::guest_msrs). + /// [`SandboxBuilder::guest_msrs`](crate::SandboxBuilder::guest_msrs). /// /// ## Sandbox status /// @@ -355,12 +351,9 @@ impl MultiUseSandbox { /// # Examples /// /// ```no_run - /// # use hyperlight_host::{MultiUseSandbox, UninitializedSandbox, GuestBinary}; + /// # use hyperlight_host::SandboxBuilder; /// # fn example() -> Result<(), Box> { - /// let mut sandbox: MultiUseSandbox = UninitializedSandbox::new( - /// GuestBinary::FilePath("guest.bin".into()), - /// None - /// )?.evolve()?; + /// let mut sandbox = SandboxBuilder::new().build_from_file("guest.bin")?; /// /// // Modify sandbox state /// sandbox.call_guest_function_by_name::("SetValue", 42)?; @@ -457,7 +450,7 @@ impl MultiUseSandbox { /// /// On x86_64, this restores the MSR state captured by /// [`MultiUseSandbox::snapshot`]: - /// [`SandboxConfiguration::guest_msrs`](crate::sandbox::SandboxConfiguration::guest_msrs) + /// [`SandboxBuilder::guest_msrs`](crate::SandboxBuilder::guest_msrs) /// selects which MSRs are saved and restored. /// /// Restore writes the snapshot's saved MSRs. On KVM the destination must @@ -489,12 +482,9 @@ impl MultiUseSandbox { /// # Examples /// /// ```no_run - /// # use hyperlight_host::{MultiUseSandbox, UninitializedSandbox, GuestBinary}; + /// # use hyperlight_host::SandboxBuilder; /// # fn example() -> Result<(), Box> { - /// let mut sandbox: MultiUseSandbox = UninitializedSandbox::new( - /// GuestBinary::FilePath("guest.bin".into()), - /// None - /// )?.evolve()?; + /// let mut sandbox = SandboxBuilder::new().build_from_file("guest.bin")?; /// /// // Take initial snapshot from this sandbox /// let snapshot = sandbox.snapshot()?; @@ -515,12 +505,9 @@ impl MultiUseSandbox { /// ## Recovering from Poison /// /// ```no_run - /// # use hyperlight_host::{MultiUseSandbox, UninitializedSandbox, GuestBinary, HyperlightError}; + /// # use hyperlight_host::SandboxBuilder; /// # fn example() -> Result<(), Box> { - /// let mut sandbox: MultiUseSandbox = UninitializedSandbox::new( - /// GuestBinary::FilePath("guest.bin".into()), - /// None - /// )?.evolve()?; + /// let mut sandbox = SandboxBuilder::new().build_from_file("guest.bin")?; /// /// // Take snapshot before potentially poisoning operation /// let snapshot = sandbox.snapshot()?; @@ -648,12 +635,9 @@ impl MultiUseSandbox { /// # Examples /// /// ```no_run - /// # use hyperlight_host::{MultiUseSandbox, UninitializedSandbox, GuestBinary}; + /// # use hyperlight_host::SandboxBuilder; /// # fn example() -> Result<(), Box> { - /// let mut sandbox: MultiUseSandbox = UninitializedSandbox::new( - /// GuestBinary::FilePath("guest.bin".into()), - /// None - /// )?.evolve()?; + /// let mut sandbox = SandboxBuilder::new().build_from_file("guest.bin")?; /// /// // Call function with no arguments /// let result: i32 = sandbox.call_guest_function_by_name("GetCounter", ())?; @@ -713,12 +697,9 @@ impl MultiUseSandbox { /// # Examples /// /// ```no_run - /// # use hyperlight_host::{MultiUseSandbox, UninitializedSandbox, GuestBinary}; + /// # use hyperlight_host::SandboxBuilder; /// # fn example() -> Result<(), Box> { - /// let mut sandbox: MultiUseSandbox = UninitializedSandbox::new( - /// GuestBinary::FilePath("guest.bin".into()), - /// None - /// )?.evolve()?; + /// let mut sandbox = SandboxBuilder::new().build_from_file("guest.bin")?; /// /// // Call function with no arguments /// let result: i32 = sandbox.call("GetCounter", ())?; @@ -741,12 +722,9 @@ impl MultiUseSandbox { /// ## Handling Potential Poisoning /// /// ```no_run - /// # use hyperlight_host::{MultiUseSandbox, UninitializedSandbox, GuestBinary}; + /// # use hyperlight_host::SandboxBuilder; /// # fn example() -> Result<(), Box> { - /// let mut sandbox: MultiUseSandbox = UninitializedSandbox::new( - /// GuestBinary::FilePath("guest.bin".into()), - /// None - /// )?.evolve()?; + /// let mut sandbox = SandboxBuilder::new().build_from_file("guest.bin")?; /// /// // Take snapshot before risky operation /// let snapshot = sandbox.snapshot()?; @@ -990,14 +968,11 @@ impl MultiUseSandbox { /// # Examples /// /// ```no_run - /// # use hyperlight_host::{MultiUseSandbox, UninitializedSandbox, GuestBinary}; + /// # use hyperlight_host::SandboxBuilder; /// # use std::thread; /// # use std::time::Duration; /// # fn example() -> Result<(), Box> { - /// let mut sandbox: MultiUseSandbox = UninitializedSandbox::new( - /// GuestBinary::FilePath("guest.bin".into()), - /// None - /// )?.evolve()?; + /// let mut sandbox = SandboxBuilder::new().build_from_file("guest.bin")?; /// /// // Get interrupt handle before starting long-running operation /// let interrupt_handle = sandbox.interrupt_handle(); @@ -1096,12 +1071,9 @@ impl MultiUseSandbox { /// # Examples /// /// ```no_run - /// # use hyperlight_host::{MultiUseSandbox, UninitializedSandbox, GuestBinary, SandboxStatus}; + /// # use hyperlight_host::SandboxBuilder; /// # fn example() -> Result<(), Box> { - /// let mut sandbox: MultiUseSandbox = UninitializedSandbox::new( - /// GuestBinary::FilePath("guest.bin".into()), - /// None - /// )?.evolve()?; + /// let mut sandbox = SandboxBuilder::new().build_from_file("guest.bin")?; /// /// if sandbox.status().is_poisoned() { /// println!("Sandbox is poisoned"); @@ -1200,7 +1172,8 @@ mod tests { use crate::mem::shared_mem::{ExclusiveSharedMemory, GuestSharedMemory, SharedMemory as _}; use crate::sandbox::SandboxConfiguration; use crate::{ - GuestBinary, HyperlightError, MultiUseSandbox, Result, SandboxStatus, UninitializedSandbox, + GuestBinary, HyperlightError, MultiUseSandbox, Result, SandboxBuilder, SandboxStatus, + UninitializedSandbox, }; #[test] @@ -1220,12 +1193,9 @@ mod tests { #[test] fn poison() { - let mut sbox: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve() - } - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let snapshot = sbox.snapshot().unwrap(); // poison on purpose @@ -1310,13 +1280,12 @@ mod tests { #[test] fn host_func_error() { let path = simple_guest_as_pathbuf(); - let mut sandbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - sandbox - .register("HostError", || -> Result<()> { + let mut sandbox = SandboxBuilder::new() + .host_function("HostError", || -> Result<()> { Err(HyperlightError::Error("hi".to_string())) }) + .build_from_file(path) .unwrap(); - let mut sandbox = sandbox.evolve().unwrap(); // will exhaust io if leaky for _ in 0..1000 { @@ -1336,8 +1305,7 @@ mod tests { #[test] fn call_host_func_expect_error() { let path = simple_guest_as_pathbuf(); - let sandbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - let mut sandbox = sandbox.evolve().unwrap(); + let mut sandbox = SandboxBuilder::new().build_from_file(path).unwrap(); sandbox .call::<()>("CallHostExpectError", "SomeUnknownHostFunc".to_string()) .unwrap(); @@ -1346,14 +1314,13 @@ mod tests { /// Make sure input/output buffers are properly reset after guest call (with host call) #[test] fn io_buffer_reset() { - let mut cfg = SandboxConfiguration::default(); - cfg.set_input_data_size(4096); - cfg.set_output_data_size(4096); let path = simple_guest_as_pathbuf(); - let mut sandbox = - UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg)).unwrap(); - sandbox.register("HostAdd", |a: i32, b: i32| a + b).unwrap(); - let mut sandbox = sandbox.evolve().unwrap(); + let mut sandbox = SandboxBuilder::new() + .input_data_size(4096) + .output_data_size(4096) + .host_function("HostAdd", |a: i32, b: i32| a + b) + .build_from_file(path) + .unwrap(); // will exhaust io if leaky. Tests both success and error paths for _ in 0..1000 { @@ -1369,12 +1336,9 @@ mod tests { /// Tests that call_guest_function_by_name restores the state correctly #[test] fn test_call_guest_function_by_name() { - let mut sbox: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve() - } - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let snapshot = sbox.snapshot().unwrap(); @@ -1396,35 +1360,35 @@ mod tests { // This test effectively ensures that the stack is being properly reset after each call and we are not leaking memory in the Guest. #[test] fn test_with_small_stack_and_heap() { - let mut cfg = SandboxConfiguration::default(); - cfg.set_heap_size(32 * 1024); + const HEAP_SIZE: u64 = 32 * 1024; // min_scratch_size already includes 1 page (4k on most // platforms) of guest stack, so add 20k more to get 24k // total, and then add some more for the eagerly-copied page // tables on amd64 - let min_scratch = hyperlight_common::layout::min_scratch_size( - cfg.get_input_data_size(), - cfg.get_output_data_size(), - ); - cfg.set_scratch_size(min_scratch + 0x10000 + 0x10000); + let scratch_size = { + let defaults = SandboxBuilder::new(); + hyperlight_common::layout::min_scratch_size( + defaults.get_input_data_size(), + defaults.get_output_data_size(), + ) + } + 0x10000 + + 0x10000; - let mut sbox1: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg)).unwrap(); - u_sbox.evolve() - } - .unwrap(); + let mut sbox1 = SandboxBuilder::new() + .heap_size(HEAP_SIZE) + .scratch_size(scratch_size) + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); for _ in 0..1000 { sbox1.call::("Echo", "hello".to_string()).unwrap(); } - let mut sbox2: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg)).unwrap(); - u_sbox.evolve() - } - .unwrap(); + let mut sbox2 = SandboxBuilder::new() + .heap_size(HEAP_SIZE) + .scratch_size(scratch_size) + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); for i in 0..1000 { sbox2 @@ -1440,12 +1404,9 @@ mod tests { /// and restoring a snapshot from before evolving restores the previous state #[test] fn snapshot_evolve_restore_handles_state_correctly() { - let mut sbox: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve() - } - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let snapshot = sbox.snapshot().unwrap(); @@ -1461,11 +1422,9 @@ mod tests { #[test] fn test_trigger_exception_on_guest() { - let usbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap(); - - let mut multi_use_sandbox: MultiUseSandbox = usbox.evolve().unwrap(); + let mut multi_use_sandbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let res: Result<()> = multi_use_sandbox.call("TriggerException", ()); @@ -1496,10 +1455,7 @@ mod tests { for _ in 0..SANDBOXES_PER_THREAD { let guest_path = simple_guest_as_pathbuf(); - let uninit = - UninitializedSandbox::new(GuestBinary::FilePath(guest_path), None).unwrap(); - - let mut sandbox: MultiUseSandbox = uninit.evolve().unwrap(); + let mut sandbox = SandboxBuilder::new().build_from_file(guest_path).unwrap(); let result: i32 = sandbox.call("GetStatic", ()).unwrap(); assert_eq!(result, 0); @@ -1533,11 +1489,9 @@ mod tests { #[test] fn test_mmap() { - let mut sbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let expected = b"hello world"; let map_mem = page_aligned_memory(expected); @@ -1566,11 +1520,9 @@ mod tests { // Makes sure MemoryRegionFlags::READ | MemoryRegionFlags::EXECUTE executable but not writable #[test] fn test_mmap_write_exec() { - let mut sbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); #[cfg(target_arch = "x86_64")] let expected = &[0x90, 0x90, 0x90, 0xC3]; // NOOP slide to RET @@ -1645,11 +1597,9 @@ mod tests { #[test] fn snapshot_restore_handles_remapping_correctly() { - let mut sbox: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve().unwrap() - }; + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); // 1. Take snapshot 1 with no additional regions mapped let snapshot1 = sbox.snapshot().unwrap(); @@ -1713,11 +1663,9 @@ mod tests { /// target ever mapping the region. #[test] fn snapshot_restore_across_sandboxes_preserves_mapped_region_contents() { - let mut source: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve().unwrap() - }; + let mut source = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let map_mem = allocate_guest_memory(); let guest_base = 0x200000000_usize; @@ -1738,11 +1686,9 @@ mod tests { let snapshot = source.snapshot().unwrap(); - let mut target: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve().unwrap() - }; + let mut target = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); assert_eq!(target.vm.get_mapped_regions().count(), 0); target.restore(snapshot).unwrap(); @@ -1765,17 +1711,13 @@ mod tests { #[test] fn snapshot_restore_across_sandboxes() { - let mut sandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve().unwrap() - }; + let mut sandbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); - let mut sandbox2 = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve().unwrap() - }; + let mut sandbox2 = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); sandbox.call::("AddToStatic", 42i32).unwrap(); assert_eq!(sandbox2.call::("GetStatic", ()).unwrap(), 0); @@ -2080,21 +2022,15 @@ mod tests { #[test] fn snapshot_restore_rejects_incompatible_layout() { - let mut sandbox = { - let path = simple_guest_as_pathbuf(); - let mut cfg = SandboxConfiguration::default(); - cfg.set_heap_size(0x10_000); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg)).unwrap(); - u_sbox.evolve().unwrap() - }; + let mut sandbox = SandboxBuilder::new() + .heap_size(0x10_000) + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); - let mut sandbox2 = { - let path = simple_guest_as_pathbuf(); - let mut cfg = SandboxConfiguration::default(); - cfg.set_heap_size(0x20_000); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg)).unwrap(); - u_sbox.evolve().unwrap() - }; + let mut sandbox2 = SandboxBuilder::new() + .heap_size(0x20_000) + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let snapshot = sandbox.snapshot().unwrap(); let err = sandbox2.restore(snapshot); @@ -2105,20 +2041,14 @@ mod tests { /// rejected `restore` leaves the target usable. #[test] fn snapshot_restore_failure_leaves_target_usable() { - let path = simple_guest_as_pathbuf(); - let mut cfg_a = SandboxConfiguration::default(); - cfg_a.set_heap_size(0x10_000); - let mut source = UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg_a)) - .unwrap() - .evolve() + let mut source = SandboxBuilder::new() + .heap_size(0x10_000) + .build_from_file(simple_guest_as_pathbuf()) .unwrap(); - let path = simple_guest_as_pathbuf(); - let mut cfg_b = SandboxConfiguration::default(); - cfg_b.set_heap_size(0x20_000); - let mut target = UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg_b)) - .unwrap() - .evolve() + let mut target = SandboxBuilder::new() + .heap_size(0x20_000) + .build_from_file(simple_guest_as_pathbuf()) .unwrap(); target.call::("AddToStatic", 5i32).unwrap(); @@ -2141,19 +2071,15 @@ mod tests { /// unmaps anything the target had mapped. #[test] fn snapshot_restore_across_sandboxes_target_has_mapped_regions() { - let mut source: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve().unwrap() - }; + let mut source = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); source.call::("AddToStatic", 23i32).unwrap(); let snapshot = source.snapshot().unwrap(); - let mut target: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve().unwrap() - }; + let mut target = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let map_mem = allocate_guest_memory(); let guest_base = 0x200000000_usize; let region = region_for_memory(&map_mem, guest_base, MemoryRegionFlags::READ); @@ -2170,11 +2096,9 @@ mod tests { /// GVA. #[test] fn snapshot_restore_across_sandboxes_both_have_different_mapped_regions() { - let mut source: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve().unwrap() - }; + let mut source = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let source_mem = allocate_guest_memory(); let source_base = 0x200000000_usize; let source_region = region_for_memory(&source_mem, source_base, MemoryRegionFlags::READ); @@ -2192,11 +2116,9 @@ mod tests { source.call::("AddToStatic", 9i32).unwrap(); let snapshot = source.snapshot().unwrap(); - let mut target: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve().unwrap() - }; + let mut target = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let target_mem = allocate_guest_memory(); let target_base = 0x300000000_usize; let target_region = region_for_memory(&target_mem, target_base, MemoryRegionFlags::READ); @@ -2224,19 +2146,15 @@ mod tests { /// Repeated restore of the same snapshot is idempotent. #[test] fn snapshot_restore_across_sandboxes_repeated() { - let mut source: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve().unwrap() - }; + let mut source = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); source.call::("AddToStatic", 7i32).unwrap(); let snapshot = source.snapshot().unwrap(); - let mut target: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve().unwrap() - }; + let mut target = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); target.restore(snapshot.clone()).unwrap(); assert_eq!(target.call::("GetStatic", ()).unwrap(), 7); @@ -2252,11 +2170,9 @@ mod tests { /// that restore() calls reset_vcpu(). #[test] fn snapshot_restore_resets_debug_registers() { - let mut sandbox: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve().unwrap() - }; + let mut sandbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let snapshot = sandbox.snapshot().unwrap(); @@ -2328,11 +2244,9 @@ mod tests { /// leak into the next call. #[test] fn stale_abort_buffer_does_not_leak_across_calls() { - let mut sbox: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve().unwrap() - }; + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); // Simulate a partial abort sbox.mem_mgr.abort_buffer.extend_from_slice(&[0xAA; 1020]); @@ -2361,15 +2275,12 @@ mod tests { ]; for (name, heap_size) in test_cases { - let mut cfg = SandboxConfiguration::default(); - cfg.set_heap_size(heap_size); - cfg.set_scratch_size(0x100000); - let path = simple_guest_as_pathbuf(); - let sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg)) - .unwrap_or_else(|e| panic!("Failed to create {} sandbox: {}", name, e)) - .evolve() - .unwrap_or_else(|e| panic!("Failed to evolve {} sandbox: {}", name, e)); + let sbox = SandboxBuilder::new() + .heap_size(heap_size) + .scratch_size(0x100000) + .build_from_file(path) + .unwrap_or_else(|e| panic!("Failed to create {} sandbox: {}", name, e)); drop(sbox); } @@ -2379,10 +2290,7 @@ mod tests { #[cfg(feature = "trace_guest")] fn sandbox_for_gva_tests() -> MultiUseSandbox { let path = simple_guest_as_pathbuf(); - UninitializedSandbox::new(GuestBinary::FilePath(path), None) - .unwrap() - .evolve() - .unwrap() + SandboxBuilder::new().build_from_file(path).unwrap() } /// Helper: read memory at `gva` of length `len` from the guest side via @@ -2496,11 +2404,9 @@ mod tests { let (path, expected_bytes) = create_test_file("hyperlight_test_map_file_cow_basic.bin", expected); - let mut sbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let guest_base: u64 = 0x1_0000_0000; let mapped_size = sbox.map_file_cow(&path, guest_base).unwrap(); @@ -2534,11 +2440,9 @@ mod tests { let content = &[0xBB; 4096]; let (path, _) = create_test_file("hyperlight_test_map_file_cow_readonly.bin", content); - let mut sbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let guest_base: u64 = 0x1_0000_0000; sbox.map_file_cow(&path, guest_base).unwrap(); @@ -2566,12 +2470,9 @@ mod tests { fn test_map_file_cow_poisoned() { let (path, _) = create_test_file("hyperlight_test_map_file_cow_poison.bin", &[0xCC; 4096]); - let mut sbox: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve() - } - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let snapshot = sbox.snapshot().unwrap(); // Poison the sandbox @@ -2603,17 +2504,13 @@ mod tests { let guest_base: u64 = 0x1_0000_0000; - let mut sbox1 = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sbox1 = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); - let mut sbox2 = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sbox2 = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); // Map the same file into both sandboxes sbox1.map_file_cow(&path, guest_base).unwrap(); @@ -2667,13 +2564,9 @@ mod tests { handles.push(thread::spawn(move || { barrier.wait(); - let mut sbox = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_pathbuf()), - None, - ) - .unwrap() - .evolve() - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let guest_base: u64 = 0x1_0000_0000; sbox.map_file_cow(&path, guest_base).unwrap(); @@ -2704,11 +2597,9 @@ mod tests { let (path, _) = create_test_file("hyperlight_test_map_file_cow_cleanup.bin", &[0xDD; 4096]); { - let mut sbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); sbox.map_file_cow(&path, 0x1_0000_0000).unwrap(); // sandbox dropped here @@ -2727,11 +2618,9 @@ mod tests { let (path, expected_bytes) = create_test_file("hyperlight_test_map_file_cow_snapshot_remap.bin", expected); - let mut sbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let guest_base: u64 = 0x1_0000_0000; @@ -2791,11 +2680,9 @@ mod tests { let (path, expected_bytes) = create_test_file("hyperlight_test_map_file_cow_snap_restore.bin", expected); - let mut sbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let guest_base: u64 = 0x1_0000_0000; sbox.map_file_cow(&path, guest_base).unwrap(); @@ -2854,7 +2741,7 @@ mod tests { ); // Evolve — deferred mappings are applied during this step. - let mut sbox: MultiUseSandbox = u_sbox.evolve().unwrap(); + let mut sbox = u_sbox.evolve().unwrap(); // Verify the guest can read the mapped content. let actual: Vec = sbox @@ -2999,11 +2886,9 @@ mod tests { #[test] fn map_region_rejects_overlapping_regions() { - let mut sbox: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve().unwrap() - }; + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let mem1 = allocate_guest_memory(); let mem2 = allocate_guest_memory(); @@ -3024,11 +2909,9 @@ mod tests { #[test] fn map_region_rejects_partial_overlap() { - let mut sbox: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve().unwrap() - }; + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); // Use multi-page regions so partial overlap is geometrically possible let ps = page_size::get(); @@ -3051,11 +2934,9 @@ mod tests { #[test] fn map_region_allows_adjacent_non_overlapping() { - let mut sbox: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve().unwrap() - }; + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let mem1 = allocate_guest_memory(); let mem2 = allocate_guest_memory(); @@ -3073,11 +2954,9 @@ mod tests { #[test] fn map_region_rejects_overlap_with_snapshot() { - let mut sbox: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve().unwrap() - }; + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); // Try to map at BASE_ADDRESS (0x1000) which overlaps the snapshot region let mem = allocate_guest_memory(); @@ -3095,11 +2974,9 @@ mod tests { #[test] fn map_region_rejects_overlap_with_scratch() { - let mut sbox: MultiUseSandbox = { - let path = simple_guest_as_pathbuf(); - let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u_sbox.evolve().unwrap() - }; + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); // The scratch region occupies the top of the GPA space let scratch_addr = hyperlight_common::layout::scratch_base_gpa( @@ -3117,7 +2994,6 @@ mod tests { #[cfg(target_arch = "x86_64")] mod msr_tests { use super::*; - use crate::HostFunctions; use crate::hypervisor::hyperlight_vm::{CreateHyperlightVmError, HyperlightVmError}; use crate::hypervisor::regs::{ MSR_APERF, MSR_BNDCFGS, MSR_CSTAR, MSR_DEBUGCTL, MSR_IA32_SSP, @@ -3161,11 +3037,9 @@ mod tests { #[test] fn kernel_gs_base_does_not_leak_through_swapgs() { - let mut sandbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sandbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let original: u64 = sandbox.call("ReadKernelGsBaseViaSwapgs", ()).unwrap(); let sentinel = if original == 0x0000_7AAA_5555_AAAA { @@ -3197,15 +3071,11 @@ mod tests { #[test] fn snapshot_msr_values_survive_full_in_memory_lifecycle() { - let mut config = SandboxConfiguration::default(); - config.guest_msrs(&[KERNEL_GS_BASE]).unwrap(); - let mut source = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_pathbuf()), - Some(config), - ) - .unwrap() - .evolve() - .unwrap(); + let mut source = SandboxBuilder::new() + .guest_msrs(&[KERNEL_GS_BASE]) + .unwrap() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let first = 0x1111; let second = 0x2222; let third = 0x3333; @@ -3232,12 +3102,11 @@ mod tests { first ); - let mut clone = MultiUseSandbox::from_snapshot( - first_snapshot.clone(), - HostFunctions::default(), - Some(config), - ) - .unwrap(); + let mut clone = SandboxBuilder::new() + .guest_msrs(&[KERNEL_GS_BASE]) + .unwrap() + .build_from_snapshot(first_snapshot.clone()) + .unwrap(); assert_eq!(clone.call::("ReadMSR", KERNEL_GS_BASE).unwrap(), first); clone @@ -3251,12 +3120,11 @@ mod tests { third ); - let mut second_clone = MultiUseSandbox::from_snapshot( - third_snapshot, - HostFunctions::default(), - Some(config), - ) - .unwrap(); + let mut second_clone = SandboxBuilder::new() + .guest_msrs(&[KERNEL_GS_BASE]) + .unwrap() + .build_from_snapshot(third_snapshot) + .unwrap(); assert_eq!( second_clone.call::("ReadMSR", KERNEL_GS_BASE).unwrap(), third @@ -3272,15 +3140,11 @@ mod tests { fn equivalent_msr_configs_are_order_independent_across_sandboxes() { let source_order = [KERNEL_GS_BASE, SYSENTER_CS]; let target_order = [SYSENTER_CS, KERNEL_GS_BASE]; - let mut source_config = SandboxConfiguration::default(); - source_config.guest_msrs(&source_order).unwrap(); - let mut source = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_pathbuf()), - Some(source_config), - ) - .unwrap() - .evolve() - .unwrap(); + let mut source = SandboxBuilder::new() + .guest_msrs(&source_order) + .unwrap() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); source .call::<()>("WriteMSR", (KERNEL_GS_BASE, 0x4444u64)) .unwrap(); @@ -3294,15 +3158,11 @@ mod tests { assert_eq!(source.call::("ReadMSR", SYSENTER_CS).unwrap(), 0x5555); let snapshot = source.snapshot().unwrap(); - let mut target_config = SandboxConfiguration::default(); - target_config.guest_msrs(&target_order).unwrap(); - let mut target = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_pathbuf()), - Some(target_config), - ) - .unwrap() - .evolve() - .unwrap(); + let mut target = SandboxBuilder::new() + .guest_msrs(&target_order) + .unwrap() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); target .call::<()>("WriteMSR", (KERNEL_GS_BASE, 0xAAAAu64)) .unwrap(); @@ -3321,12 +3181,11 @@ mod tests { ); assert_eq!(target.call::("ReadMSR", SYSENTER_CS).unwrap(), 0x5555); - let mut clone = MultiUseSandbox::from_snapshot( - snapshot, - HostFunctions::default(), - Some(target_config), - ) - .unwrap(); + let mut clone = SandboxBuilder::new() + .guest_msrs(&target_order) + .unwrap() + .build_from_snapshot(snapshot) + .unwrap(); assert_eq!( clone.call::("ReadMSR", KERNEL_GS_BASE).unwrap(), 0x4444 @@ -3341,41 +3200,29 @@ mod tests { fn snapshot_restores_into_superset_guest_msrs() { const SYSENTER_ESP: u32 = 0x175; let sentinel: u64 = 0x1234; - let mut source_config = SandboxConfiguration::default(); - source_config.guest_msrs(&[SYSENTER_CS]).unwrap(); - let mut source = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_pathbuf()), - Some(source_config), - ) - .unwrap() - .evolve() - .unwrap(); + let mut source = SandboxBuilder::new() + .guest_msrs(&[SYSENTER_CS]) + .unwrap() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); source .call::<()>("WriteMSR", (SYSENTER_CS, sentinel)) .unwrap(); let snapshot = source.snapshot().unwrap(); - let mut dest_config = SandboxConfiguration::default(); - dest_config + let mut clone = SandboxBuilder::new() .guest_msrs(&[SYSENTER_CS, SYSENTER_ESP]) + .unwrap() + .build_from_snapshot(snapshot.clone()) .unwrap(); - - let mut clone = MultiUseSandbox::from_snapshot( - snapshot.clone(), - HostFunctions::default(), - Some(dest_config), - ) - .unwrap(); assert_eq!(clone.call::("ReadMSR", SYSENTER_CS).unwrap(), sentinel); let baseline: u64 = clone.call("ReadMSR", SYSENTER_ESP).unwrap(); - let mut target = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_pathbuf()), - Some(dest_config), - ) - .unwrap() - .evolve() - .unwrap(); + let mut target = SandboxBuilder::new() + .guest_msrs(&[SYSENTER_CS, SYSENTER_ESP]) + .unwrap() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); target .call::<()>("WriteMSR", (SYSENTER_ESP, baseline ^ 0x55)) .unwrap(); @@ -3398,15 +3245,11 @@ mod tests { #[test] fn snapshot_rejects_non_superset_guest_msrs() { const SYSENTER_ESP: u32 = 0x175; - let mut source_config = SandboxConfiguration::default(); - source_config.guest_msrs(&[SYSENTER_CS]).unwrap(); - let mut source = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_pathbuf()), - Some(source_config), - ) - .unwrap() - .evolve() - .unwrap(); + let mut source = SandboxBuilder::new() + .guest_msrs(&[SYSENTER_CS]) + .unwrap() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); source .call::<()>("WriteMSR", (SYSENTER_CS, 0x1234u64)) .unwrap(); @@ -3416,24 +3259,18 @@ mod tests { // disjoint MSR, both reject because the snapshot's SYSENTER_CS is // neither declared by the destination nor a core MSR. for dest in [&[][..], &[SYSENTER_ESP][..]] { - let mut config = SandboxConfiguration::default(); - config.guest_msrs(dest).unwrap(); - - let err = MultiUseSandbox::from_snapshot( - snapshot.clone(), - HostFunctions::default(), - Some(config), - ) - .expect_err("from_snapshot must reject an unrestorable snapshot MSR"); + let err = SandboxBuilder::new() + .guest_msrs(dest) + .unwrap() + .build_from_snapshot(snapshot.clone()) + .expect_err("from_snapshot must reject an unrestorable snapshot MSR"); assert_snapshot_msr_index_invalid(&err); - let mut target = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_pathbuf()), - Some(config), - ) - .unwrap() - .evolve() - .unwrap(); + let mut target = SandboxBuilder::new() + .guest_msrs(dest) + .unwrap() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let err = target .restore(snapshot.clone()) .expect_err("restore must reject an unrestorable snapshot MSR"); @@ -3456,12 +3293,11 @@ mod tests { ); assert!(snapshot.msrs().is_none()); - let mut sandbox = MultiUseSandbox::from_snapshot( - snapshot.clone(), - HostFunctions::default(), - Some(config), - ) - .unwrap(); + let mut sandbox = SandboxBuilder::new() + .guest_msrs(&[KERNEL_GS_BASE]) + .unwrap() + .build_from_snapshot(snapshot.clone()) + .unwrap(); let baseline: u64 = sandbox.call("ReadMSR", KERNEL_GS_BASE).unwrap(); sandbox .call::<()>("WriteMSR", (KERNEL_GS_BASE, baseline ^ 0x55)) @@ -3486,11 +3322,9 @@ mod tests { const MSR_X2APIC_BASE: u32 = 0x800; const APIC_BASE_DEFAULT: u64 = 0xFEE0_0900; - let mut sandbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sandbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let snapshot = sandbox.snapshot().unwrap(); let x2apic_base = APIC_BASE_DEFAULT | APIC_BASE_X2APIC_ENABLE; @@ -3523,11 +3357,9 @@ mod tests { } } - let mut sbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let snapshot = sbox.snapshot().unwrap(); let msr_index: u32 = 0xC000_0102; // IA32_KERNEL_GS_BASE @@ -3556,11 +3388,9 @@ mod tests { #[test] #[cfg(target_arch = "x86_64")] fn nested_virtualization_is_hidden_from_guest() { - let mut sandbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sandbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let features: u32 = sandbox.call("NestedVirtualizationCpuid", ()).unwrap(); assert_eq!(features & 0b11, 0, "guest CPUID exposes VMX or SVM"); @@ -3575,11 +3405,9 @@ mod tests { return; } - let mut sandbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sandbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let snapshot = sandbox.snapshot().unwrap(); let vmx_basic: u32 = 0x480; @@ -3605,11 +3433,9 @@ mod tests { #[test] #[cfg(target_arch = "x86_64")] fn guest_cannot_enter_vmx_operation() { - let mut sandbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sandbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let result = sandbox.call::<()>("EnableVmxOperation", ()); assert!( @@ -3626,11 +3452,9 @@ mod tests { #[test] #[cfg(target_arch = "x86_64")] fn guest_vmlaunch_faults() { - let mut sandbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sandbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let result = sandbox.call::<()>("ExecuteVmlaunch", ()); assert!( @@ -3651,11 +3475,9 @@ mod tests { return; } - let mut sandbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sandbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); assert!( !sandbox.call::("X2apicSupported", ()).unwrap(), @@ -3667,16 +3489,12 @@ mod tests { #[test] #[cfg(target_arch = "x86_64")] fn test_allow_non_resettable_msr_fails_creation() { - let mut cfg = SandboxConfiguration::default(); - cfg.guest_msrs(&[0x49]).unwrap(); // IA32_PRED_CMD, a write-only command MSR - - let err = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_pathbuf()), - Some(cfg), - ) - .unwrap() - .evolve() - .unwrap_err(); + // IA32_PRED_CMD, a write-only command MSR + let err = SandboxBuilder::new() + .guest_msrs(&[0x49]) + .unwrap() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap_err(); assert_msr_not_declarable(&err, 0x49); } @@ -3691,16 +3509,12 @@ mod tests { return; } - let mut cfg = SandboxConfiguration::default(); - cfg.guest_msrs(&[0x1A0]).unwrap(); // IA32_MISC_ENABLE: host-probeable, not in MSR_TABLE - - let err = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_pathbuf()), - Some(cfg), - ) - .unwrap() - .evolve() - .expect_err("an unclassified declared MSR must be rejected at creation"); + // IA32_MISC_ENABLE: host-probeable, not in MSR_TABLE + let err = SandboxBuilder::new() + .guest_msrs(&[0x1A0]) + .unwrap() + .build_from_file(simple_guest_as_pathbuf()) + .expect_err("an unclassified declared MSR must be rejected at creation"); assert_msr_not_declarable(&err, 0x1A0); } @@ -3710,16 +3524,12 @@ mod tests { fn test_multiple_guest_msrs_reset_across_restore() { // Resettable MSRs the guest may write once declared. let msrs: [u32; 4] = [0x174, 0x175, 0x176, 0xC000_0102]; - let mut cfg = SandboxConfiguration::default(); - cfg.guest_msrs(&msrs).unwrap(); - let mut sbox = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_pathbuf()), - Some(cfg), - ) - .unwrap() - .evolve() - .unwrap(); + let mut sbox = SandboxBuilder::new() + .guest_msrs(&msrs) + .unwrap() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let baseline_snapshot = sbox.snapshot().unwrap(); @@ -3747,15 +3557,11 @@ mod tests { let msr_index: u32 = 0xC000_0102; // IA32_KERNEL_GS_BASE let sentinel: u64 = 0xCAFE_F00D; - let mut cfg = SandboxConfiguration::default(); - cfg.guest_msrs(&[msr_index]).unwrap(); - let mut sbox = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_pathbuf()), - Some(cfg), - ) - .unwrap() - .evolve() - .unwrap(); + let mut sbox = SandboxBuilder::new() + .guest_msrs(&[msr_index]) + .unwrap() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let baseline = sbox.snapshot().unwrap(); let original: u64 = sbox.call("ReadMSR", msr_index).unwrap(); @@ -3788,13 +3594,9 @@ mod tests { } for msr_index in [0x1D9_u32, 0x800] { - let mut sbox = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_pathbuf()), - None, - ) - .unwrap() - .evolve() - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let result = sbox.call::<()>("WriteMSR", (msr_index, 0x1u64)); assert!( @@ -3820,11 +3622,9 @@ mod tests { const KVM_CUSTOM_MSR_START: u32 = 0x4B56_4D00; const KVM_CUSTOM_MSR_END: u32 = 0x4B56_4DFF; - let mut sandbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sandbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let snapshot = sandbox.snapshot().unwrap(); for index in KVM_CUSTOM_MSR_START..=KVM_CUSTOM_MSR_END { @@ -3861,11 +3661,9 @@ mod tests { (0xC001_0117, "AMD VM_HSAVE_PA"), ]; - let mut sbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); for &(msr, _name) in cases { assert_msr_write_does_not_survive_restore(&mut sbox, msr, 0x1); @@ -3877,11 +3675,9 @@ mod tests { #[test] #[cfg(target_arch = "x86_64")] fn misc_enable_guest_write_does_not_survive_restore() { - let mut sbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); assert_msr_write_does_not_survive_restore(&mut sbox, 0x1A0, 1u64 << 40); } @@ -3899,11 +3695,9 @@ mod tests { #[cfg(not(kvm))] let is_kvm = false; - let mut sbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let reset_indices: Vec = sbox.vm.reset_set_indices(); @@ -4132,11 +3926,9 @@ mod tests { return; } - let mut sbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let baseline = sbox.snapshot().unwrap(); @@ -4280,11 +4072,9 @@ mod tests { #[test] #[cfg(all(any(mshv3, target_os = "windows"), target_arch = "x86_64"))] fn active_ssp_does_not_leak_across_restore() { - let mut sbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); if !sbox.call::("CetShadowStackSupported", ()).unwrap() { return; @@ -4320,11 +4110,9 @@ mod tests { return; } - let mut sbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None) - .unwrap() - .evolve() - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); assert!( !sbox.call::("CetShadowStackSupported", ()).unwrap(), "KVM guest CPUID exposes CET shadow stacks" @@ -4332,15 +4120,11 @@ mod tests { // With CET hidden the host cannot read or write IA32_S_CET, so // allowing it is rejected at VM creation. - let mut cfg = SandboxConfiguration::default(); - cfg.guest_msrs(&[MSR_S_CET]).unwrap(); - let err = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_pathbuf()), - Some(cfg), - ) - .unwrap() - .evolve() - .expect_err("allowing IA32_S_CET must be rejected when CET is hidden"); + let err = SandboxBuilder::new() + .guest_msrs(&[MSR_S_CET]) + .unwrap() + .build_from_file(simple_guest_as_pathbuf()) + .expect_err("allowing IA32_S_CET must be rejected when CET is hidden"); assert_msr_not_declarable(&err, MSR_S_CET); } } @@ -4354,25 +4138,20 @@ mod tests { use crate::func::Registerable; use crate::sandbox::SandboxConfiguration; use crate::sandbox::snapshot::Snapshot; - use crate::{ - GuestBinary, HostFunctions, HyperlightError, MultiUseSandbox, UninitializedSandbox, - }; + use crate::{GuestBinary, HostFunctions, HyperlightError, MultiUseSandbox, SandboxBuilder}; fn make_sandbox() -> MultiUseSandbox { let path = simple_guest_as_pathbuf(); - UninitializedSandbox::new(GuestBinary::FilePath(path), None) - .unwrap() - .evolve() - .unwrap() + SandboxBuilder::new().build_from_file(path).unwrap() } /// Sandbox with an extra `Add(i32, i32) -> i32` host function. fn make_sandbox_with_add() -> MultiUseSandbox { let path = simple_guest_as_pathbuf(); - let mut u = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u.register_host_function("Add", |a: i32, b: i32| Ok(a + b)) - .unwrap(); - u.evolve().unwrap() + SandboxBuilder::new() + .host_function("Add", |a: i32, b: i32| a + b) + .build_from_file(path) + .unwrap() } fn host_funcs_with_matching_add() -> HostFunctions { @@ -4387,8 +4166,7 @@ mod tests { let mut sbox = make_sandbox(); sbox.call::("AddToStatic", 11i32).unwrap(); let snapshot = sbox.snapshot().unwrap(); - let mut sbox2 = - MultiUseSandbox::from_snapshot(snapshot, HostFunctions::default(), None).unwrap(); + let mut sbox2 = SandboxBuilder::new().build_from_snapshot(snapshot).unwrap(); assert_eq!(sbox2.call::("GetStatic", ()).unwrap(), 11); let echoed: String = sbox2.call("Echo", "hi".to_string()).unwrap(); assert_eq!(echoed, "hi"); @@ -4400,9 +4178,9 @@ mod tests { let snap = Snapshot::from_env(GuestBinary::FilePath(path), SandboxConfiguration::default()) .unwrap(); - let mut sbox = - MultiUseSandbox::from_snapshot(Arc::new(snap), HostFunctions::default(), None) - .unwrap(); + let mut sbox = SandboxBuilder::new() + .build_from_snapshot(Arc::new(snap)) + .unwrap(); assert_eq!(sbox.call::("GetStatic", ()).unwrap(), 0); } @@ -4415,12 +4193,12 @@ mod tests { sbox.call::("AddToStatic", 3i32).unwrap(); let snapshot = sbox.snapshot().unwrap(); - let mut a = - MultiUseSandbox::from_snapshot(snapshot.clone(), HostFunctions::default(), None) - .unwrap(); - let mut b = - MultiUseSandbox::from_snapshot(snapshot.clone(), HostFunctions::default(), None) - .unwrap(); + let mut a = SandboxBuilder::new() + .build_from_snapshot(snapshot.clone()) + .unwrap(); + let mut b = SandboxBuilder::new() + .build_from_snapshot(snapshot.clone()) + .unwrap(); assert_eq!(a.call::("GetStatic", ()).unwrap(), 3); assert_eq!(b.call::("GetStatic", ()).unwrap(), 3); @@ -4439,8 +4217,10 @@ mod tests { let mut sbox = make_sandbox_with_add(); sbox.call::("AddToStatic", 5i32).unwrap(); let snap = sbox.snapshot().unwrap(); - let mut sbox2 = - MultiUseSandbox::from_snapshot(snap, host_funcs_with_matching_add(), None).unwrap(); + let mut sbox2 = SandboxBuilder::new() + .host_functions(host_funcs_with_matching_add()) + .build_from_snapshot(snap) + .unwrap(); assert_eq!(sbox2.call::("GetStatic", ()).unwrap(), 5); } @@ -4448,7 +4228,8 @@ mod tests { fn rejects_missing_host_function() { let mut sbox = make_sandbox_with_add(); let snap = sbox.snapshot().unwrap(); - let err = MultiUseSandbox::from_snapshot(snap, HostFunctions::default(), None) + let err = SandboxBuilder::new() + .build_from_snapshot(snap) .expect_err("missing `Add` must be rejected"); assert!( matches!( @@ -4492,10 +4273,10 @@ mod tests { let mut sbox_with_add = make_sandbox_with_add(); let snap = sbox_with_add.snapshot().unwrap(); let path = simple_guest_as_pathbuf(); - let mut u = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u.register_host_function("Add", |a: String, b: String| Ok(format!("{a}{b}"))) + let mut sbox_wrong_add = SandboxBuilder::new() + .host_function("Add", |a: String, b: String| format!("{a}{b}")) + .build_from_file(path) .unwrap(); - let mut sbox_wrong_add = u.evolve().unwrap(); let err = sbox_wrong_add .restore(snap) .expect_err("signature mismatch on `Add` must be rejected on restore"); @@ -4519,12 +4300,11 @@ mod tests { let snap = source.snapshot().unwrap(); let path = simple_guest_as_pathbuf(); - let mut u = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u.register_host_function("Add", |a: i32, b: i32| Ok(a + b)) + let mut target = SandboxBuilder::new() + .host_function("Add", |a: i32, b: i32| a + b) + .host_function("Mul", |a: i32, b: i32| a * b) + .build_from_file(path) .unwrap(); - u.register_host_function("Mul", |a: i32, b: i32| Ok(a * b)) - .unwrap(); - let mut target = u.evolve().unwrap(); target.restore(snap).unwrap(); assert_eq!(target.call::("GetStatic", ()).unwrap(), 17); @@ -4537,7 +4317,9 @@ mod tests { let mut hf = HostFunctions::default(); hf.register_host_function("Add", |a: String, b: String| Ok(format!("{a}{b}"))) .unwrap(); - let err = MultiUseSandbox::from_snapshot(snap, hf, None) + let err = SandboxBuilder::new() + .host_functions(hf) + .build_from_snapshot(snap) .expect_err("signature mismatch on `Add` must be rejected"); assert!( matches!( @@ -4560,7 +4342,10 @@ mod tests { let mut hf = host_funcs_with_matching_add(); hf.register_host_function("Mul", |a: i32, b: i32| Ok(a * b)) .unwrap(); - let mut sbox2 = MultiUseSandbox::from_snapshot(snap, hf, None).unwrap(); + let mut sbox2 = SandboxBuilder::new() + .host_functions(hf) + .build_from_snapshot(snap) + .unwrap(); assert_eq!(sbox2.call::("GetStatic", ()).unwrap(), 9); } @@ -4572,8 +4357,7 @@ mod tests { sbox.call::("AddToStatic", 4i32).unwrap(); let snap1 = sbox.snapshot().unwrap(); - let mut sbox2 = - MultiUseSandbox::from_snapshot(snap1, HostFunctions::default(), None).unwrap(); + let mut sbox2 = SandboxBuilder::new().build_from_snapshot(snap1).unwrap(); sbox2.call::("AddToStatic", 6i32).unwrap(); let snap2 = sbox2.snapshot().unwrap(); @@ -4583,8 +4367,7 @@ mod tests { sbox2.restore(snap2.clone()).unwrap(); assert_eq!(sbox2.call::("GetStatic", ()).unwrap(), 10); - let mut sbox3 = - MultiUseSandbox::from_snapshot(snap2, HostFunctions::default(), None).unwrap(); + let mut sbox3 = SandboxBuilder::new().build_from_snapshot(snap2).unwrap(); assert_eq!(sbox3.call::("GetStatic", ()).unwrap(), 10); } @@ -4593,14 +4376,18 @@ mod tests { #[test] fn supplied_host_function_is_callable() { let path = simple_guest_as_pathbuf(); - let mut u = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u.register_host_function("Echo42", || Ok(1i64)).unwrap(); - let mut sbox = u.evolve().unwrap(); + let mut sbox = SandboxBuilder::new() + .host_function("Echo42", || 1i64) + .build_from_file(path) + .unwrap(); let snap = sbox.snapshot().unwrap(); let mut hf = HostFunctions::default(); hf.register_host_function("Echo42", || Ok(42i64)).unwrap(); - let mut sbox2 = MultiUseSandbox::from_snapshot(snap, hf, None).unwrap(); + let mut sbox2 = SandboxBuilder::new() + .host_functions(hf) + .build_from_snapshot(snap) + .unwrap(); let got: i64 = sbox2 .call( @@ -4622,7 +4409,10 @@ mod tests { let mut hf = HostFunctions::default(); hf.register_host_function("Unrelated", |a: i32| Ok(a + 1)) .unwrap(); - let mut sbox = MultiUseSandbox::from_snapshot(Arc::new(snap), hf, None).unwrap(); + let mut sbox = SandboxBuilder::new() + .host_functions(hf) + .build_from_snapshot(Arc::new(snap)) + .unwrap(); assert_eq!(sbox.call::("GetStatic", ()).unwrap(), 0); } @@ -4640,8 +4430,7 @@ mod tests { let gen2 = snap2.snapshot_generation(); assert_eq!(gen2, gen1 + 1); - let mut sbox2 = - MultiUseSandbox::from_snapshot(snap2, HostFunctions::default(), None).unwrap(); + let mut sbox2 = SandboxBuilder::new().build_from_snapshot(snap2).unwrap(); sbox2.call::("AddToStatic", 1i32).unwrap(); let snap3 = sbox2.snapshot().unwrap(); assert_eq!(snap3.snapshot_generation(), gen2 + 1); @@ -4663,7 +4452,8 @@ mod tests { // host function, so building a sandbox from it without // `Echo42` must fail. let snap = sbox.snapshot().unwrap(); - let err = MultiUseSandbox::from_snapshot(snap, HostFunctions::default(), None) + let err = SandboxBuilder::new() + .build_from_snapshot(snap) .expect_err("late-registered `Echo42` must be required by the new snapshot"); let msg = format!("{}", err); assert!(msg.contains("Echo42"), "got: {}", msg); diff --git a/src/hyperlight_host/src/sandbox/snapshot/file/mod.rs b/src/hyperlight_host/src/sandbox/snapshot/file/mod.rs index 590a1f6011..678d1d1626 100644 --- a/src/hyperlight_host/src/sandbox/snapshot/file/mod.rs +++ b/src/hyperlight_host/src/sandbox/snapshot/file/mod.rs @@ -324,13 +324,10 @@ impl Snapshot { /// # Examples /// /// ```no_run - /// # use hyperlight_host::{MultiUseSandbox, UninitializedSandbox, GuestBinary}; + /// # use hyperlight_host::SandboxBuilder; /// # use hyperlight_host::sandbox::snapshot::OciTag; /// # fn example() -> Result<(), Box> { - /// let mut sandbox: MultiUseSandbox = UninitializedSandbox::new( - /// GuestBinary::FilePath("guest.bin".into()), - /// None, - /// )?.evolve()?; + /// let mut sandbox = SandboxBuilder::new().build_from_file("guest.bin")?; /// /// // Capture the initialized state and write it to an OCI layout on disk. /// let snapshot = sandbox.snapshot()?; diff --git a/src/hyperlight_host/src/sandbox/snapshot/file_tests.rs b/src/hyperlight_host/src/sandbox/snapshot/file_tests.rs index 033db0fd98..7e200a50e4 100644 --- a/src/hyperlight_host/src/sandbox/snapshot/file_tests.rs +++ b/src/hyperlight_host/src/sandbox/snapshot/file_tests.rs @@ -15,22 +15,16 @@ use crate::func::Registerable; use crate::mem::layout::SandboxMemoryLayout; use crate::mem::shared_mem::SharedMemory as _; use crate::sandbox::snapshot::{OciDigest, OciReference, OciTag, Snapshot}; -use crate::{GuestBinary, HostFunctions, MultiUseSandbox, UninitializedSandbox}; +use crate::{GuestBinary, HostFunctions, MultiUseSandbox, SandboxBuilder}; fn create_test_sandbox() -> MultiUseSandbox { let path = simple_guest_as_pathbuf(); - UninitializedSandbox::new(GuestBinary::FilePath(path), None) - .unwrap() - .evolve() - .unwrap() + SandboxBuilder::new().build_from_file(path).unwrap() } fn create_c_test_sandbox() -> MultiUseSandbox { let path = c_simple_guest_as_pathbuf(); - UninitializedSandbox::new(GuestBinary::FilePath(path), None) - .unwrap() - .evolve() - .unwrap() + SandboxBuilder::new().build_from_file(path).unwrap() } fn random_sequence(sandbox: &mut MultiUseSandbox) -> [i32; 4] { @@ -273,13 +267,11 @@ fn disk_snapshot_restores_declared_msr_value() { const SYSENTER_CS: u32 = 0x174; let sentinel: u64 = 0xDEAD_BEEF; - let mut cfg = crate::sandbox::SandboxConfiguration::default(); - cfg.guest_msrs(&[SYSENTER_CS]).unwrap(); - let mut source = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), Some(cfg)) - .unwrap() - .evolve() - .unwrap(); + let mut source = SandboxBuilder::new() + .guest_msrs(&[SYSENTER_CS]) + .unwrap() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); source .call::<()>("WriteMSR", (SYSENTER_CS, sentinel)) .unwrap(); @@ -290,11 +282,11 @@ fn disk_snapshot_restores_declared_msr_value() { snap.save(&path, &OciTag::new("latest").unwrap()).unwrap(); let loaded = Arc::new(Snapshot::checked_load(&path, OciTag::new("latest").unwrap()).unwrap()); - let mut cfg = crate::sandbox::SandboxConfiguration::default(); - cfg.guest_msrs(&[SYSENTER_CS]).unwrap(); - let mut sbox = - MultiUseSandbox::from_snapshot(loaded.clone(), HostFunctions::default(), Some(cfg)) - .unwrap(); + let mut sbox = SandboxBuilder::new() + .guest_msrs(&[SYSENTER_CS]) + .unwrap() + .build_from_snapshot(loaded.clone()) + .unwrap(); assert_eq!(sbox.call::("ReadMSR", SYSENTER_CS).unwrap(), sentinel); sbox.call::<()>("WriteMSR", (SYSENTER_CS, sentinel ^ 0x55)) @@ -312,13 +304,11 @@ fn disk_snapshot_restores_into_superset_guest_msrs() { const SYSENTER_ESP: u32 = 0x175; let sentinel: u64 = 0xDEAD_BEEF; - let mut cfg = crate::sandbox::SandboxConfiguration::default(); - cfg.guest_msrs(&[SYSENTER_CS]).unwrap(); - let mut source = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), Some(cfg)) - .unwrap() - .evolve() - .unwrap(); + let mut source = SandboxBuilder::new() + .guest_msrs(&[SYSENTER_CS]) + .unwrap() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); source .call::<()>("WriteMSR", (SYSENTER_CS, sentinel)) .unwrap(); @@ -329,10 +319,11 @@ fn disk_snapshot_restores_into_superset_guest_msrs() { snap.save(&path, &OciTag::new("latest").unwrap()).unwrap(); let loaded = Arc::new(Snapshot::checked_load(&path, OciTag::new("latest").unwrap()).unwrap()); - let mut dest_cfg = crate::sandbox::SandboxConfiguration::default(); - dest_cfg.guest_msrs(&[SYSENTER_CS, SYSENTER_ESP]).unwrap(); - let mut sbox = - MultiUseSandbox::from_snapshot(loaded, HostFunctions::default(), Some(dest_cfg)).unwrap(); + let mut sbox = SandboxBuilder::new() + .guest_msrs(&[SYSENTER_CS, SYSENTER_ESP]) + .unwrap() + .build_from_snapshot(loaded) + .unwrap(); assert_eq!(sbox.call::("ReadMSR", SYSENTER_CS).unwrap(), sentinel); } @@ -346,13 +337,11 @@ fn disk_snapshot_non_superset_guest_msrs_rejected() { const SYSENTER_CS: u32 = 0x174; let sentinel: u64 = 0x1234; - let mut cfg = crate::sandbox::SandboxConfiguration::default(); - cfg.guest_msrs(&[SYSENTER_CS]).unwrap(); - let mut source = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), Some(cfg)) - .unwrap() - .evolve() - .unwrap(); + let mut source = SandboxBuilder::new() + .guest_msrs(&[SYSENTER_CS]) + .unwrap() + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); source .call::<()>("WriteMSR", (SYSENTER_CS, sentinel)) .unwrap(); @@ -739,10 +728,10 @@ fn call_snapshot_without_sregs_rejected() { /// custom `Add(i32, i32) -> i32`. fn create_sandbox_with_custom_host_funcs() -> MultiUseSandbox { let path = simple_guest_as_pathbuf(); - let mut u = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u.register_host_function("Add", |a: i32, b: i32| Ok(a + b)) - .unwrap(); - u.evolve().unwrap() + SandboxBuilder::new() + .host_function("Add", |a: i32, b: i32| Ok(a + b)) + .build_from_file(path) + .unwrap() } /// `HostFunctions::default()` plus a matching `Add(i32, i32) -> i32`. @@ -836,9 +825,10 @@ fn from_snapshot_accepts_extra_host_functions() { #[test] fn from_snapshot_accepts_zero_arg_host_function() { let path = simple_guest_as_pathbuf(); - let mut u = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - u.register_host_function("Zero", || Ok(7i64)).unwrap(); - let mut sbox = u.evolve().unwrap(); + let mut sbox = SandboxBuilder::new() + .host_function("Zero", || Ok(7i64)) + .build_from_file(path) + .unwrap(); let snap = sbox.snapshot().unwrap(); let dir = tempfile::tempdir().unwrap(); @@ -2580,15 +2570,14 @@ fn index_json_too_large_on_write_rejected() { #[test] fn config_blob_too_large_on_write_rejected() { let guest = simple_guest_as_pathbuf(); - let mut u = UninitializedSandbox::new(GuestBinary::FilePath(guest), None).unwrap(); + let mut builder = SandboxBuilder::new(); // Each host function adds its name and signature to the config // JSON. Long names reach the 1 MiB cap with a modest count. let long = "h".repeat(300); for i in 0..3000 { - u.register_host_function(&format!("{long}{i}"), |a: i32, b: i32| Ok(a + b)) - .unwrap(); + builder = builder.host_function(format!("{long}{i}"), |a: i32, b: i32| Ok(a + b)); } - let mut sbox = u.evolve().unwrap(); + let mut sbox = builder.build_from_file(guest).unwrap(); let snap = sbox.snapshot().unwrap(); let dir = tempfile::tempdir().unwrap(); @@ -2778,15 +2767,11 @@ fn round_trip_preserves_stack_top_gva() { #[test] fn round_trip_preserves_non_default_scratch_size() { - use crate::sandbox::SandboxConfiguration; - let mut cfg = SandboxConfiguration::default(); let custom_scratch: usize = 256 * 1024; - cfg.set_scratch_size(custom_scratch); - let mut sbox = - UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), Some(cfg)) - .unwrap() - .evolve() - .unwrap(); + let mut sbox = SandboxBuilder::new() + .scratch_size(custom_scratch) + .build_from_file(simple_guest_as_pathbuf()) + .unwrap(); let snap = sbox.snapshot().unwrap(); let original = snap.layout().get_scratch_size(); assert_eq!(original, custom_scratch); @@ -3158,17 +3143,15 @@ fn read_blob_dir( // `from_snapshot` config plumbing. // ============================================================================= // -// `from_snapshot` accepts a caller-supplied `SandboxConfiguration`. +// A builder building from a snapshot accepts caller-supplied settings. // Layout fields must be silently overridden by the snapshot (the // on-disk memory blob already encodes those sizes). Runtime fields // must take effect. -/// Layout fields supplied via `SandboxConfiguration` must be silently -/// overridden. The snapshot's own layout is authoritative. +/// Layout fields supplied to the builder must be silently overridden. +/// The snapshot's own layout is authoritative. #[test] fn from_snapshot_silently_ignores_layout_overrides() { - use crate::sandbox::SandboxConfiguration; - let mut sbox = create_test_sandbox(); let snapshot = sbox.snapshot().unwrap(); let original_input = snapshot.layout().input_data_size(); @@ -3176,15 +3159,13 @@ fn from_snapshot_silently_ignores_layout_overrides() { let original_heap = snapshot.layout().heap_size(); let original_scratch = snapshot.layout().get_scratch_size(); - let mut config = SandboxConfiguration::default(); - config.set_input_data_size(original_input * 2); - config.set_output_data_size(original_output * 2); - config.set_heap_size((original_heap as u64) * 2); - config.set_scratch_size(original_scratch * 2); - - let mut sbox2 = - MultiUseSandbox::from_snapshot(snapshot.clone(), HostFunctions::default(), Some(config)) - .unwrap(); + let mut sbox2 = SandboxBuilder::new() + .input_data_size(original_input * 2) + .output_data_size(original_output * 2) + .heap_size((original_heap as u64) * 2) + .scratch_size(original_scratch * 2) + .build_from_snapshot(snapshot.clone()) + .unwrap(); sbox2.call::("GetStatic", ()).unwrap(); @@ -3288,21 +3269,18 @@ fn persisted_guest_libc_rng_snapshot_reseeds_each_instance() { assert_ne!(random_sequence(&mut first), random_sequence(&mut second)); } -/// `from_snapshot` honors `guest_core_dump=true` so that +/// Building from a snapshot honors `guest_core_dump=true` so that /// `generate_crashdump_to_dir` writes a file. #[test] #[cfg(crashdump)] fn from_snapshot_honors_guest_core_dump_enabled() { - use crate::sandbox::SandboxConfiguration; - let mut sbox = create_test_sandbox(); let snapshot = sbox.snapshot().unwrap(); - let mut config = SandboxConfiguration::default(); - config.set_guest_core_dump(true); - - let mut sbox2 = - MultiUseSandbox::from_snapshot(snapshot, HostFunctions::default(), Some(config)).unwrap(); + let mut sbox2 = SandboxBuilder::new() + .guest_core_dump(true) + .build_from_snapshot(snapshot) + .unwrap(); let dir = tempfile::tempdir().unwrap(); sbox2.generate_crashdump_to_dir(dir.path()).unwrap(); @@ -3317,21 +3295,18 @@ fn from_snapshot_honors_guest_core_dump_enabled() { ); } -/// `from_snapshot` honors `guest_core_dump=false` so that +/// Building from a snapshot honors `guest_core_dump=false` so that /// `generate_crashdump_to_dir` produces no file. #[test] #[cfg(crashdump)] fn from_snapshot_honors_guest_core_dump_disabled() { - use crate::sandbox::SandboxConfiguration; - let mut sbox = create_test_sandbox(); let snapshot = sbox.snapshot().unwrap(); - let mut config = SandboxConfiguration::default(); - config.set_guest_core_dump(false); - - let mut sbox2 = - MultiUseSandbox::from_snapshot(snapshot, HostFunctions::default(), Some(config)).unwrap(); + let mut sbox2 = SandboxBuilder::new() + .guest_core_dump(false) + .build_from_snapshot(snapshot) + .unwrap(); let dir = tempfile::tempdir().unwrap(); sbox2.generate_crashdump_to_dir(dir.path()).unwrap(); @@ -3355,20 +3330,12 @@ fn from_snapshot_honors_guest_core_dump_disabled() { #[test] fn round_trip_preserves_non_default_init_data_permissions() { use crate::mem::memory_region::MemoryRegionFlags; - use crate::sandbox::uninitialized::{GuestBlob, GuestEnvironment}; let path = simple_guest_as_pathbuf(); let data: &[u8] = b"perm-pinned-init-data"; - let env = GuestEnvironment { - guest_binary: GuestBinary::FilePath(path), - init_data: Some(GuestBlob { - data, - permissions: MemoryRegionFlags::READ | MemoryRegionFlags::WRITE, - }), - }; - let mut sbox = UninitializedSandbox::new(env, None) - .unwrap() - .evolve() + let mut sbox = SandboxBuilder::new() + .init_data(data, MemoryRegionFlags::READ | MemoryRegionFlags::WRITE) + .build_from_file(path) .unwrap(); let snap = sbox.snapshot().unwrap(); let expected = snap.layout().init_data_permissions(); diff --git a/src/hyperlight_host/src/sandbox/uninitialized.rs b/src/hyperlight_host/src/sandbox/uninitialized.rs index 94e3eee1d6..066c80c194 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized.rs @@ -428,7 +428,7 @@ mod tests { 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 mut sandbox = uninitialized_sandbox.evolve().unwrap(); let res = sandbox .call::>("ReadFromUserMemory", (4u64, buffer.to_vec())) @@ -473,7 +473,7 @@ mod tests { // Get a Sandbox from an uninitialized sandbox without a call back function - let _sandbox: MultiUseSandbox = uninitialized_sandbox.evolve().unwrap(); + let _sandbox = uninitialized_sandbox.evolve().unwrap(); // Test with a valid guest binary buffer @@ -1153,8 +1153,8 @@ mod tests { .expect("Failed to create second sandbox from snapshot"); // Both should be able to evolve independently - let _evolved1: MultiUseSandbox = sandbox1.evolve().expect("Failed to evolve sandbox1"); - let _evolved2: MultiUseSandbox = sandbox2.evolve().expect("Failed to evolve sandbox2"); + let _evolved1 = sandbox1.evolve().expect("Failed to evolve sandbox1"); + let _evolved2 = sandbox2.evolve().expect("Failed to evolve sandbox2"); } // Test 2: Create snapshot with custom heap size @@ -1177,7 +1177,7 @@ mod tests { ) .expect("Failed to create sandbox from snapshot with custom heap"); - let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox"); + let _evolved = sandbox.evolve().expect("Failed to evolve sandbox"); } // Test 3: Create snapshot with custom scratch size @@ -1200,7 +1200,7 @@ mod tests { ) .expect("Failed to create sandbox from snapshot with custom stack"); - let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox"); + let _evolved = sandbox.evolve().expect("Failed to evolve sandbox"); } // Test 4: Create snapshot with custom input/output buffer sizes @@ -1224,7 +1224,7 @@ mod tests { ) .expect("Failed to create sandbox from snapshot with custom buffers"); - let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox"); + let _evolved = sandbox.evolve().expect("Failed to evolve sandbox"); } // Test 5: Create snapshot with all custom settings @@ -1265,9 +1265,9 @@ mod tests { ) .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 _evolved1 = sandbox1.evolve().expect("Failed to evolve sandbox1"); + let _evolved2 = sandbox2.evolve().expect("Failed to evolve sandbox2"); + let _evolved3 = sandbox3.evolve().expect("Failed to evolve sandbox3"); } // Test 6: Create snapshot from binary buffer instead of file path @@ -1287,7 +1287,7 @@ mod tests { ) .expect("Failed to create sandbox from buffer-based snapshot"); - let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox"); + let _evolved = sandbox.evolve().expect("Failed to evolve sandbox"); } // Test 7: Register host functions on sandboxes created from snapshot @@ -1311,7 +1311,7 @@ mod tests { .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 evolved = sandbox.evolve().expect("Failed to evolve sandbox"); // Verify the host function was registered let host_funcs = evolved @@ -1348,7 +1348,7 @@ mod tests { ) .expect("Failed to create sandbox from snapshot with init data"); - let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox"); + let _evolved = sandbox.evolve().expect("Failed to evolve sandbox"); } // Test 9: Create snapshot from existing sandbox diff --git a/src/hyperlight_host/tests/common/mod.rs b/src/hyperlight_host/tests/common/mod.rs index adecb62767..141e1adb14 100644 --- a/src/hyperlight_host/tests/common/mod.rs +++ b/src/hyperlight_host/tests/common/mod.rs @@ -3,9 +3,7 @@ use std::path::PathBuf; -use hyperlight_host::func::HostFunction; -use hyperlight_host::sandbox::SandboxConfiguration; -use hyperlight_host::{GuestBinary, MultiUseSandbox, UninitializedSandbox}; +use hyperlight_host::{MultiUseSandbox, SandboxBuilder}; use hyperlight_testing::{c_simple_guest_as_pathbuf, simple_guest_as_pathbuf}; /// Returns the path to the Rust simple guest binary. @@ -18,127 +16,84 @@ fn c_guest_path() -> PathBuf { c_simple_guest_as_pathbuf() } -/// Creates a new Rust guest MultiUseSandbox. -pub fn new_rust_sandbox() -> MultiUseSandbox { - UninitializedSandbox::new(GuestBinary::FilePath(rust_guest_path()), None) - .unwrap() - .evolve() - .unwrap() -} - -/// Creates a new Rust guest UninitializedSandbox. -pub fn new_rust_uninit_sandbox() -> UninitializedSandbox { - UninitializedSandbox::new(GuestBinary::FilePath(rust_guest_path()), None).unwrap() -} - // ============================================================================= // Rust guest helpers // ============================================================================= +/// Builds a Rust guest MultiUseSandbox from `builder`. +pub fn build_rust_sandbox(builder: SandboxBuilder) -> MultiUseSandbox { + builder.build_from_file(rust_guest_path()).unwrap() +} + +/// Creates a new Rust guest MultiUseSandbox. +pub fn new_rust_sandbox() -> MultiUseSandbox { + build_rust_sandbox(SandboxBuilder::new()) +} + /// Runs a test with a Rust guest MultiUseSandbox. pub fn with_rust_sandbox(f: F) where F: FnOnce(MultiUseSandbox), { - let sandbox = UninitializedSandbox::new(GuestBinary::FilePath(rust_guest_path()), None) - .unwrap() - .evolve() - .unwrap(); - f(sandbox); + f(new_rust_sandbox()); } -/// Runs a test with a Rust guest MultiUseSandbox using custom configuration. -pub fn with_rust_sandbox_cfg(cfg: SandboxConfiguration, f: F) +/// Runs a test with a Rust guest MultiUseSandbox built from `builder`. +pub fn with_rust_sandbox_from(builder: SandboxBuilder, f: F) where F: FnOnce(MultiUseSandbox), { - let sandbox = UninitializedSandbox::new(GuestBinary::FilePath(rust_guest_path()), Some(cfg)) - .unwrap() - .evolve() - .unwrap(); - f(sandbox); -} - -/// Runs a test with a Rust guest UninitializedSandbox. -pub fn with_rust_uninit_sandbox(f: F) -where - F: FnOnce(UninitializedSandbox), -{ - let sandbox = - UninitializedSandbox::new(GuestBinary::FilePath(rust_guest_path()), None).unwrap(); - f(sandbox); + f(build_rust_sandbox(builder)); } // ============================================================================= // C guest helpers // ============================================================================= +/// Builds a C guest MultiUseSandbox from `builder`. +pub fn build_c_sandbox(builder: SandboxBuilder) -> MultiUseSandbox { + builder.build_from_file(c_guest_path()).unwrap() +} + /// Runs a test with a C guest MultiUseSandbox. pub fn with_c_sandbox(f: F) where F: FnOnce(MultiUseSandbox), { - let sandbox = UninitializedSandbox::new(GuestBinary::FilePath(c_guest_path()), None) - .unwrap() - .evolve() - .unwrap(); - f(sandbox); + f(build_c_sandbox(SandboxBuilder::new())); } -/// Runs a test with a C guest UninitializedSandbox. -pub fn with_c_uninit_sandbox(f: F) +/// Runs a test with a C guest MultiUseSandbox built from `builder`. +pub fn with_c_sandbox_from(builder: SandboxBuilder, f: F) where - F: FnOnce(UninitializedSandbox), + F: FnOnce(MultiUseSandbox), { - let sandbox = UninitializedSandbox::new(GuestBinary::FilePath(c_guest_path()), None).unwrap(); - f(sandbox); + f(build_c_sandbox(builder)); } // ============================================================================= // Both guests helpers (run test with Rust AND C guests) // ============================================================================= -/// Runs a test with both Rust and C guest MultiUseSandboxes. -pub fn with_all_sandboxes_cfg(cfg: Option, f: F) +/// Runs a test once per guest binary, passing the path to it. +/// +/// Use this when the test needs to configure the sandbox itself, for instance +/// to register a host function that owns per-guest state. +pub fn with_all_guests(f: F) where - F: Fn(MultiUseSandbox), + F: Fn(PathBuf), { for path in [rust_guest_path(), c_guest_path()] { - let sandbox = UninitializedSandbox::new(GuestBinary::FilePath(path), cfg) - .unwrap() - .evolve() - .unwrap(); - f(sandbox); + f(path); } } + /// Runs a test with both Rust and C guest MultiUseSandboxes. pub fn with_all_sandboxes(f: F) where F: Fn(MultiUseSandbox), { - with_all_sandboxes_cfg(None, f); -} - -/// Runs a test with both Rust and C guest UninitializedSandboxes. -pub fn with_all_uninit_sandboxes(f: F) -where - F: Fn(UninitializedSandbox), -{ - for path in [rust_guest_path(), c_guest_path()] { - let sandbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - f(sandbox); - } -} - -/// Runs a test with both Rust and C guest MultiUseSandboxes, with a print writer. -pub fn with_all_sandboxes_with_writer(writer: HostFunction, f: F) -where - F: Fn(MultiUseSandbox), -{ - for path in [rust_guest_path(), c_guest_path()] { - let mut sandbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap(); - sandbox.register_print(writer.clone()).unwrap(); - let sandbox = sandbox.evolve().unwrap(); - f(sandbox); - } + with_all_guests(|path| { + f(SandboxBuilder::new().build_from_file(path).unwrap()); + }); } diff --git a/src/hyperlight_host/tests/integration_test.rs b/src/hyperlight_host/tests/integration_test.rs index 80ae25d7ec..165d753fd4 100644 --- a/src/hyperlight_host/tests/integration_test.rs +++ b/src/hyperlight_host/tests/integration_test.rs @@ -7,59 +7,58 @@ use std::time::Duration; use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; use hyperlight_common::log_level::GuestLogFilter; -use hyperlight_host::sandbox::SandboxConfiguration; -use hyperlight_host::{HyperlightError, MultiUseSandbox}; +use hyperlight_host::{HyperlightError, MultiUseSandbox, SandboxBuilder}; use hyperlight_testing::simplelogger::{LOGGER, SimpleLogger}; use serial_test::serial; use tracing_core::LevelFilter; pub mod common; // pub to disable dead_code warning use crate::common::{ - new_rust_sandbox, new_rust_uninit_sandbox, with_all_sandboxes, with_c_sandbox, - with_c_uninit_sandbox, with_rust_sandbox, with_rust_sandbox_cfg, with_rust_uninit_sandbox, + build_rust_sandbox, new_rust_sandbox, with_all_sandboxes, with_c_sandbox, with_c_sandbox_from, + with_rust_sandbox, with_rust_sandbox_from, }; // A host function cannot be interrupted, but we can at least make sure after requesting to interrupt a host call, // we don't re-enter the guest again once the host call is done #[test] fn interrupt_host_call() { - with_rust_uninit_sandbox(|mut usbox| { - let barrier = Arc::new(Barrier::new(2)); - let barrier2 = barrier.clone(); - - let spin = move || { - barrier2.wait(); - thread::sleep(std::time::Duration::from_secs(1)); - Ok(()) - }; + let barrier = Arc::new(Barrier::new(2)); + let barrier2 = barrier.clone(); - usbox.register("Spin", spin).unwrap(); + let spin = move || { + barrier2.wait(); + thread::sleep(std::time::Duration::from_secs(1)); + Ok(()) + }; - let mut sandbox: MultiUseSandbox = usbox.evolve().unwrap(); - let snapshot = sandbox.snapshot().unwrap(); - let interrupt_handle = sandbox.interrupt_handle(); - assert!(!interrupt_handle.dropped()); // not yet dropped + with_rust_sandbox_from( + SandboxBuilder::new().host_function("Spin", spin), + |mut sandbox| { + let snapshot = sandbox.snapshot().unwrap(); + let interrupt_handle = sandbox.interrupt_handle(); + assert!(!interrupt_handle.dropped()); // not yet dropped - let thread = thread::spawn({ - move || { - barrier.wait(); // wait for the host function to be entered - interrupt_handle.kill(); // send kill once host call is in progress - } - }); + let thread = thread::spawn({ + move || { + barrier.wait(); // wait for the host function to be entered + interrupt_handle.kill(); // send kill once host call is in progress + } + }); - let result = sandbox.call::("CallHostSpin", ()).unwrap_err(); - assert!( - matches!(&result, HyperlightError::ExecutionCanceledByHost()), - "unexpected error: {result:?}" - ); - assert!(sandbox.status().is_poisoned()); + let result = sandbox.call::("CallHostSpin", ()).unwrap_err(); + assert!( + matches!(&result, HyperlightError::ExecutionCanceledByHost()), + "unexpected error: {result:?}" + ); + assert!(sandbox.status().is_poisoned()); - // Restore from snapshot to clear poison - sandbox.restore(snapshot.clone()).unwrap(); - assert!(sandbox.status().is_ready()); + // Restore from snapshot to clear poison + sandbox.restore(snapshot.clone()).unwrap(); + assert!(sandbox.status().is_ready()); - thread.join().unwrap(); - }); + thread.join().unwrap(); + }, + ); } /// Makes sure a running guest call can be interrupted by the host @@ -151,10 +150,10 @@ fn interrupt_guest_call_in_advance() { /// all possible interleavings, but can hopefully increases confidence somewhat. #[test] fn interrupt_same_thread() { - let mut sbox1: MultiUseSandbox = new_rust_sandbox(); - let mut sbox2: MultiUseSandbox = new_rust_sandbox(); + let mut sbox1 = new_rust_sandbox(); + let mut sbox2 = new_rust_sandbox(); let snapshot2 = sbox2.snapshot().unwrap(); - let mut sbox3: MultiUseSandbox = new_rust_sandbox(); + let mut sbox3 = new_rust_sandbox(); let barrier = Arc::new(Barrier::new(2)); let barrier2 = barrier.clone(); @@ -196,10 +195,10 @@ fn interrupt_same_thread() { /// Same test as above but with no per-iteration barrier, to get more possible interleavings. #[test] fn interrupt_same_thread_no_barrier() { - let mut sbox1: MultiUseSandbox = new_rust_sandbox(); - let mut sbox2: MultiUseSandbox = new_rust_sandbox(); + let mut sbox1 = new_rust_sandbox(); + let mut sbox2 = new_rust_sandbox(); let snapshot2 = sbox2.snapshot().unwrap(); - let mut sbox3: MultiUseSandbox = new_rust_sandbox(); + let mut sbox3 = new_rust_sandbox(); let barrier = Arc::new(Barrier::new(2)); let barrier2 = barrier.clone(); @@ -245,9 +244,9 @@ fn interrupt_same_thread_no_barrier() { // and that anther sandbox on the original thread does not get incorrectly killed #[test] fn interrupt_moved_sandbox() { - let mut sbox1: MultiUseSandbox = new_rust_sandbox(); + let mut sbox1 = new_rust_sandbox(); let snapshot1 = sbox1.snapshot().unwrap(); - let mut sbox2: MultiUseSandbox = new_rust_sandbox(); + let mut sbox2 = new_rust_sandbox(); let interrupt_handle = sbox1.interrupt_handle(); let interrupt_handle2 = sbox2.interrupt_handle(); @@ -295,11 +294,12 @@ fn interrupt_moved_sandbox() { #[cfg(target_os = "linux")] #[serial(thread_heavy)] fn interrupt_custom_signal_no_and_retry_delay() { - let mut config = SandboxConfiguration::default(); - config.set_interrupt_vcpu_sigrtmin_offset(0).unwrap(); - config.set_interrupt_retry_delay(Duration::from_secs(1)); + let builder = SandboxBuilder::new() + .interrupt_vcpu_sigrtmin_offset(0) + .unwrap() + .interrupt_retry_delay(Duration::from_secs(1)); - with_rust_sandbox_cfg(config, |mut sbox1| { + with_rust_sandbox_from(builder, |mut sbox1| { let snapshot1 = sbox1.snapshot().unwrap(); let interrupt_handle = sbox1.interrupt_handle(); assert!(!interrupt_handle.dropped()); // not yet dropped @@ -332,14 +332,11 @@ fn interrupt_custom_signal_no_and_retry_delay() { #[test] fn interrupt_spamming_host_call() { - with_rust_uninit_sandbox(|mut uninit| { - uninit - .register("HostFunc1", || { - // do nothing - }) - .unwrap(); - let mut sbox1: MultiUseSandbox = uninit.evolve().unwrap(); + let builder = SandboxBuilder::new().host_function("HostFunc1", || { + // do nothing + }); + with_rust_sandbox_from(builder, |mut sbox1| { let interrupt_handle = sbox1.interrupt_handle(); let barrier = Arc::new(Barrier::new(2)); @@ -529,9 +526,8 @@ fn guest_malloc_abort() { "precondition: size_to_allocate ({size_to_allocate}) must be > heap_size ({heap_size})" ); - let mut cfg = SandboxConfiguration::default(); - cfg.set_heap_size(heap_size); - with_rust_sandbox_cfg(cfg, |mut sbox2| { + let builder = SandboxBuilder::new().heap_size(heap_size); + with_rust_sandbox_from(builder, |mut sbox2| { let err = sbox2 .call::( "CallMalloc", // uses the rust allocator to allocate a vector on heap @@ -603,9 +599,8 @@ fn corrupt_output_back_pointer_rejected() { fn guest_panic_no_alloc() { let heap_size = 0x8000; - let mut cfg = SandboxConfiguration::default(); - cfg.set_heap_size(heap_size); - with_rust_sandbox_cfg(cfg, |mut sbox| { + let builder = SandboxBuilder::new().heap_size(heap_size); + with_rust_sandbox_from(builder, |mut sbox| { let res = sbox .call::( "ExhaustHeap", // uses the rust allocator to allocate small blocks on the heap until OOM @@ -802,13 +797,12 @@ fn log_test_messages(levelfilter: Option) { for level in filters.iter() { // Only use Rust guest because the C guest has a different signature for LogMessage // (Long vs Int for the level parameter) - with_rust_uninit_sandbox(|mut sbox| { - if let Some(levelfilter) = levelfilter { - sbox.set_max_guest_log_level(levelfilter); - } - - let mut sbox1 = sbox.evolve().unwrap(); + let mut builder = SandboxBuilder::new(); + if let Some(levelfilter) = levelfilter { + builder = builder.guest_log_level(levelfilter); + } + with_rust_sandbox_from(builder, |mut sbox1| { let level: u64 = GuestLogFilter::from(*level).into(); let message = format!("Hello from log_message level {}", level as i32); sbox1 @@ -822,12 +816,8 @@ fn log_test_messages(levelfilter: Option) { /// or not #[test] fn test_if_guest_is_able_to_get_bool_return_values_from_host() { - with_c_uninit_sandbox(|mut sbox1| { - sbox1 - .register("HostBool", |a: i32, b: i32| a + b > 10) - .unwrap(); - let mut sbox3 = sbox1.evolve().unwrap(); - + let builder = SandboxBuilder::new().host_function("HostBool", |a: i32, b: i32| a + b > 10); + with_c_sandbox_from(builder, |mut sbox3| { for i in 1..10 { if i < 6 { let res = sbox3 @@ -848,11 +838,8 @@ fn test_if_guest_is_able_to_get_bool_return_values_from_host() { /// or not #[test] fn test_if_guest_is_able_to_get_float_return_values_from_host() { - with_c_uninit_sandbox(|mut sbox1| { - sbox1 - .register("HostAddFloat", |a: f32, b: f32| a + b) - .unwrap(); - let mut sbox3 = sbox1.evolve().unwrap(); + let builder = SandboxBuilder::new().host_function("HostAddFloat", |a: f32, b: f32| a + b); + with_c_sandbox_from(builder, |mut sbox3| { let res = sbox3 .call::("GuestRetrievesFloatValue", (1.34_f32, 1.34_f32)) .unwrap(); @@ -864,11 +851,8 @@ fn test_if_guest_is_able_to_get_float_return_values_from_host() { /// or not #[test] fn test_if_guest_is_able_to_get_double_return_values_from_host() { - with_c_uninit_sandbox(|mut sbox1| { - sbox1 - .register("HostAddDouble", |a: f64, b: f64| a + b) - .unwrap(); - let mut sbox3 = sbox1.evolve().unwrap(); + let builder = SandboxBuilder::new().host_function("HostAddDouble", |a: f64, b: f64| a + b); + with_c_sandbox_from(builder, |mut sbox3| { let res = sbox3 .call::("GuestRetrievesDoubleValue", (1.34_f64, 1.34_f64)) .unwrap(); @@ -880,13 +864,10 @@ fn test_if_guest_is_able_to_get_double_return_values_from_host() { /// or not #[test] fn test_if_guest_is_able_to_get_string_return_values_from_host() { - with_c_uninit_sandbox(|mut sbox1| { - sbox1 - .register("HostAddStrings", |a: String| { - a + ", string added by Host Function" - }) - .unwrap(); - let mut sbox3 = sbox1.evolve().unwrap(); + let builder = SandboxBuilder::new().host_function("HostAddStrings", |a: String| { + a + ", string added by Host Function" + }); + with_c_sandbox_from(builder, |mut sbox3| { let res = sbox3 .call::("GuestRetrievesStringValue", ()) .unwrap(); @@ -1381,17 +1362,15 @@ fn interrupt_infinite_loop_stress_test() { let barrier = Arc::new(Barrier::new(2)); let barrier_for_host = barrier.clone(); - let mut uninit = new_rust_uninit_sandbox(); - // Register a host function that waits on the barrier - uninit - .register("WaitForKill", move || { + let mut sandbox = build_rust_sandbox(SandboxBuilder::new().host_function( + "WaitForKill", + move || { barrier_for_host.wait(); Ok(()) - }) - .unwrap(); + }, + )); - let mut sandbox = uninit.evolve().unwrap(); // Take a snapshot to restore after each kill let snapshot = sandbox.snapshot().unwrap(); @@ -1466,19 +1445,15 @@ fn interrupt_infinite_moving_loop_stress_test() { let entered_guest = Arc::new(AtomicBool::new(false)); let entered_guest_clone = entered_guest.clone(); - let mut uninit = new_rust_uninit_sandbox(); // Register a host function that waits on the barrier - uninit - .register("WaitForKill", move || { + let sandbox = + build_rust_sandbox(SandboxBuilder::new().host_function("WaitForKill", move || { entered_guest.store(true, Ordering::Relaxed); Ok(()) - }) - .unwrap(); - let uninit2 = new_rust_uninit_sandbox(); + })); // These 2 sandboxes will have the same TID - let sandbox = uninit.evolve().unwrap(); - let bait = uninit2.evolve().unwrap(); + let bait = new_rust_sandbox(); let interrupt = sandbox.interrupt_handle(); diff --git a/src/hyperlight_host/tests/sandbox_host_tests.rs b/src/hyperlight_host/tests/sandbox_host_tests.rs index a4c2a43b24..ebe943611a 100644 --- a/src/hyperlight_host/tests/sandbox_host_tests.rs +++ b/src/hyperlight_host/tests/sandbox_host_tests.rs @@ -4,17 +4,11 @@ use core::f64; use std::sync::mpsc::channel; use std::sync::{Arc, Mutex}; -use hyperlight_host::sandbox::SandboxConfiguration; -use hyperlight_host::{ - GuestBinary, HyperlightError, MultiUseSandbox, Result, UninitializedSandbox, new_error, -}; +use hyperlight_host::{HyperlightError, Result, SandboxBuilder, new_error}; use hyperlight_testing::simple_guest_as_pathbuf; pub mod common; // pub to disable dead_code warning -use crate::common::{ - with_all_sandboxes, with_all_sandboxes_cfg, with_all_sandboxes_with_writer, - with_all_uninit_sandboxes, -}; +use crate::common::{with_all_guests, with_all_sandboxes}; #[test] fn pass_byte_array() { @@ -109,9 +103,11 @@ fn invalid_guest_function_name() { #[test] fn set_static() { - let mut cfg: SandboxConfiguration = Default::default(); - cfg.set_scratch_size(0x100C000); - with_all_sandboxes_cfg(Some(cfg), |mut sandbox| { + with_all_guests(|path| { + let mut sandbox = SandboxBuilder::new() + .scratch_size(0x100C000) + .build_from_file(path) + .unwrap(); let fn_name = "SetStatic"; let res = sandbox.call::(fn_name, ()); assert!(res.is_ok()); @@ -151,7 +147,11 @@ fn multiple_parameters() { }}; } - with_all_sandboxes_with_writer(writer.into(), |mut sb| { + with_all_guests(|path| { + let mut sb = SandboxBuilder::new() + .host_print(writer.clone()) + .build_from_file(path) + .unwrap(); test_case!(sb, rx, "PrintTwoArgs", (a, b)); test_case!(sb, rx, "PrintThreeArgs", (a, b, c)); test_case!(sb, rx, "PrintFourArgs", (a, b, c, d)); @@ -198,11 +198,11 @@ fn incorrect_parameter_num() { #[test] fn small_scratch_sandbox() { - let mut cfg = SandboxConfiguration::default(); - cfg.set_scratch_size(0x48000); - cfg.set_input_data_size(0x24000); - cfg.set_output_data_size(0x24000); - let a = UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), Some(cfg)); + let a = SandboxBuilder::new() + .scratch_size(0x48000) + .input_data_size(0x24000) + .output_data_size(0x24000) + .build_from_file(simple_guest_as_pathbuf()); assert!(matches!( a.unwrap_err(), @@ -236,7 +236,11 @@ fn simple_test_helper() { let message = "hello"; let message2 = "world"; - with_all_sandboxes_with_writer(writer.into(), |mut sandbox| { + with_all_guests(|path| { + let mut sandbox = SandboxBuilder::new() + .host_print(writer.clone()) + .build_from_file(path) + .unwrap(); let res: i32 = sandbox.call("PrintOutput", message.to_string()).unwrap(); assert_eq!(res, 5); @@ -284,19 +288,19 @@ fn simple_test_parallel() { } fn callback_test_helper() { - with_all_uninit_sandboxes(|mut sandbox| { + with_all_guests(|path| { // create host function let (tx, rx) = channel(); - sandbox - .register("HostMethod1", move |msg: String| { + let mut init_sandbox = SandboxBuilder::new() + .host_function("HostMethod1", move |msg: String| { let len = msg.len(); tx.send(msg).unwrap(); Ok(len as i32) }) + .build_from_file(path) .unwrap(); // call guest function that calls host function - let mut init_sandbox: MultiUseSandbox = sandbox.evolve().unwrap(); let msg = "Hello world"; init_sandbox .call::("GuestMethod1", msg.to_string()) @@ -330,16 +334,16 @@ fn callback_test_parallel() { #[test] fn host_function_error() { - with_all_uninit_sandboxes(|mut sandbox| { + with_all_guests(|path| { // create host function - sandbox - .register("HostMethod1", |_: String| -> Result { + let mut init_sandbox = SandboxBuilder::new() + .host_function("HostMethod1", |_: String| -> Result { Err(new_error!("Host function error!")) }) + .build_from_file(path) .unwrap(); // call guest function that calls host function - let mut init_sandbox: MultiUseSandbox = sandbox.evolve().unwrap(); let msg = "Hello world"; let snapshot = init_sandbox.snapshot().unwrap(); diff --git a/src/hyperlight_host/tests/snapshot_goldens/checks.rs b/src/hyperlight_host/tests/snapshot_goldens/checks.rs index 0d8dda90bf..823f712ef4 100644 --- a/src/hyperlight_host/tests/snapshot_goldens/checks.rs +++ b/src/hyperlight_host/tests/snapshot_goldens/checks.rs @@ -13,7 +13,7 @@ use std::path::Path; use std::sync::Arc; use hyperlight_host::sandbox::snapshot::{OciTag, Snapshot}; -use hyperlight_host::{HostFunctions, MultiUseSandbox}; +use hyperlight_host::{HostFunctions, MultiUseSandbox, SandboxBuilder}; use crate::fixtures::{CALL_COUNTER_BUMP, HEAP_PATTERN_LEN, register_host_echo_fns}; @@ -48,8 +48,10 @@ impl<'a> GoldenTest<'a> { .map_err(|e| format!("Snapshot::checked_load({}): {e}", self.tag()))?; let mut funcs = HostFunctions::default(); register_host_echo_fns(&mut funcs); - MultiUseSandbox::from_snapshot(Arc::new(snap), funcs, None) - .map_err(|e| format!("MultiUseSandbox::from_snapshot({}): {e}", self.tag())) + SandboxBuilder::new() + .host_functions(funcs) + .build_from_snapshot(Arc::new(snap)) + .map_err(|e| format!("build_from_snapshot({}): {e}", self.tag())) } } @@ -295,8 +297,10 @@ fn chained_snapshot(golden: &GoldenTest) -> Result<(), String> { let loaded = Snapshot::checked_load(&layout, tag).map_err(|e| format!("checked_load: {e}"))?; let mut funcs = HostFunctions::default(); register_host_echo_fns(&mut funcs); - let mut sbox2 = MultiUseSandbox::from_snapshot(Arc::new(loaded), funcs, None) - .map_err(|e| format!("from_snapshot: {e}"))?; + let mut sbox2 = SandboxBuilder::new() + .host_functions(funcs) + .build_from_snapshot(Arc::new(loaded)) + .map_err(|e| format!("build_from_snapshot: {e}"))?; let val: i32 = sbox2 .call("GetStatic", ()) .map_err(|e| format!("GetStatic on chained: {e}"))?; diff --git a/src/hyperlight_host/tests/snapshot_goldens/fixtures.rs b/src/hyperlight_host/tests/snapshot_goldens/fixtures.rs index 7ca2f504e1..252e373d4a 100644 --- a/src/hyperlight_host/tests/snapshot_goldens/fixtures.rs +++ b/src/hyperlight_host/tests/snapshot_goldens/fixtures.rs @@ -9,9 +9,8 @@ use std::path::PathBuf; use std::sync::Arc; use hyperlight_host::func::Registerable; -use hyperlight_host::sandbox::SandboxConfiguration; use hyperlight_host::sandbox::snapshot::Snapshot; -use hyperlight_host::{GuestBinary, MultiUseSandbox, UninitializedSandbox}; +use hyperlight_host::{HostFunctions, MultiUseSandbox, SandboxBuilder}; use hyperlight_testing::simple_guest_as_pathbuf; /// Heap pattern length used by the golden. Small enough to @@ -22,17 +21,16 @@ pub(crate) const HEAP_PATTERN_LEN: u64 = 1024; /// Set by `AddToStatic(CALL_COUNTER_BUMP)` at generate time. pub(crate) const CALL_COUNTER_BUMP: i32 = 42; -/// Canonical `SandboxConfiguration` used to produce the goldens. +/// Canonical builder configuration used to produce the goldens. /// Layout knobs are deliberately bumped away from defaults so any /// silent arithmetic change in `SandboxMemoryLayout::new` shifts at /// least one region between generate-time and load-time. -fn golden_config() -> SandboxConfiguration { - let mut cfg = SandboxConfiguration::default(); - cfg.set_input_data_size(64 * 1024); - cfg.set_output_data_size(64 * 1024); - cfg.set_heap_size(256 * 1024); - cfg.set_scratch_size(512 * 1024); - cfg +fn golden_builder() -> SandboxBuilder { + SandboxBuilder::new() + .input_data_size(64 * 1024) + .output_data_size(64 * 1024) + .heap_size(256 * 1024) + .scratch_size(512 * 1024) } fn simpleguest_path() -> PathBuf { @@ -40,13 +38,12 @@ fn simpleguest_path() -> PathBuf { } pub(crate) fn generate() -> Arc { - let mut u = UninitializedSandbox::new( - GuestBinary::FilePath(simpleguest_path()), - Some(golden_config()), - ) - .expect("UninitializedSandbox::new"); - register_host_echo_fns(&mut u); - let mut sbox = u.evolve().expect("evolve"); + let mut funcs = HostFunctions::default(); + register_host_echo_fns(&mut funcs); + let mut sbox = golden_builder() + .host_functions(funcs) + .build_from_file(simpleguest_path()) + .expect("build golden sandbox"); run_canonical_calls(&mut sbox); sbox.snapshot().expect("snapshot") }