1use std::path::Path;
18use std::sync::{Arc, Mutex};
19
20use flatbuffers::FlatBufferBuilder;
21use hyperlight_common::flatbuffer_wrappers::function_call::{FunctionCall, FunctionCallType};
22use hyperlight_common::flatbuffer_wrappers::function_types::{
23 ParameterValue, ReturnType, ReturnValue,
24};
25use hyperlight_common::flatbuffer_wrappers::util::estimate_flatbuffer_capacity;
26use tracing::{Span, instrument};
27
28use super::Callable;
29use super::file_mapping::prepare_file_cow;
30use super::host_funcs::FunctionRegistry;
31use super::snapshot::Snapshot;
32use crate::func::{ParameterTuple, SupportedReturnType};
33use crate::hypervisor::InterruptHandle;
34use crate::hypervisor::hyperlight_vm::{HyperlightVm, HyperlightVmError};
35use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
36use crate::mem::mgr::SandboxMemoryManager;
37use crate::mem::shared_mem::{HostSharedMemory, SharedMemory as _};
38use crate::metrics::{
39 METRIC_GUEST_ERROR, METRIC_GUEST_ERROR_LABEL_CODE, maybe_time_and_emit_guest_call,
40};
41use crate::{HyperlightError, Result, log_then_return};
42
43pub struct MultiUseSandbox {
82 poisoned: bool,
84 pub(crate) host_funcs: Arc<Mutex<FunctionRegistry>>,
85 pub(crate) mem_mgr: SandboxMemoryManager<HostSharedMemory>,
86 vm: HyperlightVm,
87 #[cfg(gdb)]
88 dbg_mem_access_fn: Arc<Mutex<SandboxMemoryManager<HostSharedMemory>>>,
89 pub(crate) snapshot: Option<Arc<Snapshot>>,
92 pt_root_finder: Option<PtRootFinder>,
96}
97
98pub type PtRootFinder = Box<dyn Fn(&[u8], &[u8], u64) -> Vec<u64> + Send>;
109
110impl MultiUseSandbox {
111 #[instrument(skip_all, parent = Span::current(), level = "Trace")]
117 pub(super) fn from_uninit(
118 host_funcs: Arc<Mutex<FunctionRegistry>>,
119 mgr: SandboxMemoryManager<HostSharedMemory>,
120 vm: HyperlightVm,
121 #[cfg(gdb)] dbg_mem_access_fn: Arc<Mutex<SandboxMemoryManager<HostSharedMemory>>>,
122 ) -> MultiUseSandbox {
123 Self {
124 poisoned: false,
125 host_funcs,
126 mem_mgr: mgr,
127 vm,
128 #[cfg(gdb)]
129 dbg_mem_access_fn,
130 snapshot: None,
131 pt_root_finder: None,
132 }
133 }
134
135 pub fn set_pt_root_finder(&mut self, finder: PtRootFinder) {
139 self.pt_root_finder = Some(finder);
140 }
141
142 #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
203 pub fn from_snapshot(
204 snapshot: Arc<Snapshot>,
205 host_funcs: crate::HostFunctions,
206 config: Option<crate::sandbox::SandboxConfiguration>,
207 ) -> Result<Self> {
208 use rand::RngExt;
209
210 use crate::mem::ptr::RawPtr;
211 use crate::sandbox::uninitialized_evolve::set_up_hypervisor_partition;
212
213 snapshot.validate_host_functions(host_funcs.inner())?;
216
217 let host_funcs = Arc::new(Mutex::new(host_funcs.into_inner()));
218
219 let stack_top_gva = snapshot.stack_top_gva();
220 let caller_supplied_config = config.is_some();
228 let mut config = config.unwrap_or_default();
229 if caller_supplied_config {
230 warn_on_layout_override(&config, snapshot.layout());
231 }
232 config.set_input_data_size(snapshot.layout().input_data_size);
233 config.set_output_data_size(snapshot.layout().output_data_size);
234 config.set_heap_size(snapshot.layout().heap_size as u64);
235 config.set_scratch_size(snapshot.layout().get_scratch_size());
236 let load_info = snapshot.load_info();
237
238 let mgr = crate::mem::mgr::SandboxMemoryManager::from_snapshot(&snapshot)?;
239 let (mut hshm, gshm) = mgr.build()?;
240
241 let page_size = u32::try_from(page_size::get())? as usize;
242
243 #[cfg(target_os = "linux")]
244 crate::signal_handlers::setup_signal_handlers(&config)?;
245
246 #[cfg(any(crashdump, gdb))]
252 let rt_cfg = crate::sandbox::uninitialized::SandboxRuntimeConfig {
253 #[cfg(crashdump)]
254 binary_path: None,
255 #[cfg(gdb)]
256 debug_info: config.get_guest_debug_info(),
257 #[cfg(crashdump)]
258 guest_core_dump: config.get_guest_core_dump(),
259 #[cfg(crashdump)]
260 entry_point: None,
261 };
262
263 let mut vm = set_up_hypervisor_partition(
264 gshm,
265 &config,
266 stack_top_gva,
267 page_size,
268 #[cfg(any(crashdump, gdb))]
269 rt_cfg,
270 load_info,
271 )?;
272
273 let seed = {
274 let mut rng = rand::rng();
275 rng.random::<u64>()
276 };
277 let peb_addr = RawPtr::from(u64::try_from(hshm.layout.peb_address())?);
278
279 #[cfg(gdb)]
280 let dbg_mem_access_hdl = Arc::new(Mutex::new(hshm.clone()));
281
282 vm.initialise(
284 peb_addr,
285 seed,
286 &mut hshm,
287 &host_funcs,
288 None,
289 #[cfg(gdb)]
290 dbg_mem_access_hdl,
291 )
292 .map_err(crate::hypervisor::hyperlight_vm::HyperlightVmError::Initialize)?;
293
294 if matches!(snapshot.entrypoint(), super::snapshot::NextAction::Call(_)) {
298 let sregs = snapshot.sregs().ok_or_else(|| {
299 crate::new_error!("snapshot with NextAction::Call must have captured sregs")
300 })?;
301 vm.apply_sregs(hshm.layout.get_pt_base_gpa(), sregs)
302 .map_err(|e| {
303 crate::HyperlightError::HyperlightVmError(
304 crate::hypervisor::hyperlight_vm::HyperlightVmError::Restore(e.into()),
305 )
306 })?;
307 }
308
309 #[cfg(gdb)]
310 let dbg_mem_wrapper = Arc::new(Mutex::new(hshm.clone()));
311
312 let sbox = MultiUseSandbox::from_uninit(
313 host_funcs,
314 hshm,
315 vm,
316 #[cfg(gdb)]
317 dbg_mem_wrapper,
318 );
319 Ok(sbox)
320 }
321
322 #[instrument(err(Debug), skip_all, parent = Span::current())]
356 pub fn snapshot(&mut self) -> Result<Arc<Snapshot>> {
357 if self.poisoned {
358 return Err(crate::HyperlightError::PoisonedSandbox);
359 }
360
361 if let Some(snapshot) = &self.snapshot {
362 return Ok(snapshot.clone());
363 }
364 let mapped_regions_iter = self.vm.get_mapped_regions();
365 let mapped_regions_vec: Vec<MemoryRegion> = mapped_regions_iter.cloned().collect();
366 let cr3 = self
368 .vm
369 .get_root_pt()
370 .map_err(|e| HyperlightError::HyperlightVmError(e.into()))?;
371 let root_pt_gpas = if let Some(finder) = &self.pt_root_finder {
373 let roots = self.mem_mgr.shared_mem.with_contents(|snap| {
374 self.mem_mgr
375 .scratch_mem
376 .with_contents(|scratch| finder(snap, scratch, cr3))
377 })??;
378 if roots.is_empty() { vec![cr3] } else { roots }
379 } else {
380 vec![cr3]
381 };
382
383 let stack_top_gpa = self.vm.get_stack_top();
384 let sregs = self
385 .vm
386 .get_snapshot_sregs()
387 .map_err(|e| HyperlightError::HyperlightVmError(e.into()))?;
388 let entrypoint = self.vm.get_entrypoint();
389 let host_functions = (&*self.host_funcs.try_lock().map_err(|e| {
390 crate::new_error!("Error locking host_funcs at {}:{}: {}", file!(), line!(), e)
391 })?)
392 .into();
393
394 let memory_snapshot = self.mem_mgr.snapshot(
395 mapped_regions_vec,
396 &root_pt_gpas,
397 stack_top_gpa,
398 sregs,
399 entrypoint,
400 host_functions,
401 )?;
402 let snapshot = Arc::new(memory_snapshot);
403 self.snapshot = Some(snapshot.clone());
404 Ok(snapshot)
405 }
406
407 #[instrument(err(Debug), skip_all, parent = Span::current())]
490 pub fn restore(&mut self, snapshot: Arc<Snapshot>) -> Result<()> {
491 {
515 let host_funcs = self
516 .host_funcs
517 .try_lock()
518 .map_err(|e| crate::new_error!("Error locking host_funcs: {}", e))?;
519 snapshot.validate_compatibility(&self.mem_mgr.layout, &host_funcs)?;
520 }
521
522 let (gsnapshot, gscratch) = self.mem_mgr.restore_snapshot(&snapshot)?;
523 if let Some(gsnapshot) = gsnapshot {
524 self.vm
525 .update_snapshot_mapping(gsnapshot)
526 .map_err(|e| HyperlightError::HyperlightVmError(e.into()))?;
527 }
528 if let Some(gscratch) = gscratch {
529 self.vm
530 .update_scratch_mapping(gscratch)
531 .map_err(|e| HyperlightError::HyperlightVmError(e.into()))?;
532 }
533
534 let sregs = snapshot.sregs().ok_or_else(|| {
535 HyperlightError::Error("snapshot from running sandbox should have sregs".to_string())
536 })?;
537 self.vm
540 .reset_vcpu(snapshot.root_pt_gpa(), sregs)
541 .map_err(|e| {
542 self.poisoned = true;
543 HyperlightVmError::Restore(e)
544 })?;
545
546 self.vm.set_stack_top(snapshot.stack_top_gva());
547 self.vm.set_entrypoint(snapshot.entrypoint());
548
549 let current_regions: Vec<MemoryRegion> = self.vm.get_mapped_regions().cloned().collect();
550 for region in ¤t_regions {
551 self.vm
552 .unmap_region(region)
553 .map_err(HyperlightVmError::UnmapRegion)?;
554 }
555
556 self.snapshot = Some(snapshot.clone());
558
559 self.poisoned = false;
569
570 Ok(())
571 }
572
573 #[doc(hidden)]
610 #[deprecated(
611 since = "0.8.0",
612 note = "Deprecated in favour of call and snapshot/restore."
613 )]
614 #[instrument(err(Debug), skip(self, args), parent = Span::current())]
615 pub fn call_guest_function_by_name<Output: SupportedReturnType>(
616 &mut self,
617 func_name: &str,
618 args: impl ParameterTuple,
619 ) -> Result<Output> {
620 if self.poisoned {
621 return Err(crate::HyperlightError::PoisonedSandbox);
622 }
623 let snapshot = self.snapshot()?;
624 let res = self.call(func_name, args);
625 self.restore(snapshot)?;
626 res
627 }
628
629 #[instrument(err(Debug), skip(self, args), parent = Span::current())]
706 pub fn call<Output: SupportedReturnType>(
707 &mut self,
708 func_name: &str,
709 args: impl ParameterTuple,
710 ) -> Result<Output> {
711 if self.poisoned {
712 return Err(crate::HyperlightError::PoisonedSandbox);
713 }
714 self.snapshot = None;
716 maybe_time_and_emit_guest_call(func_name, || {
717 let ret = self.call_guest_function_by_name_no_reset(
718 func_name,
719 Output::TYPE,
720 args.into_value(),
721 );
722 let ret = Output::from_value(ret?)?;
725 Ok(ret)
726 })
727 }
728
729 #[instrument(err(Debug), skip(self, rgn), parent = Span::current())]
745 pub unsafe fn map_region(&mut self, rgn: &MemoryRegion) -> Result<()> {
746 if self.poisoned {
747 return Err(crate::HyperlightError::PoisonedSandbox);
748 }
749 if rgn.flags.contains(MemoryRegionFlags::WRITE) {
750 log_then_return!("TODO: Writable mappings not yet supported");
754 }
755
756 unsafe { self.vm.map_region(rgn) }.map_err(HyperlightVmError::MapRegion)?;
758 self.snapshot = None;
759 Ok(())
760 }
761
762 #[instrument(err(Debug), skip(self, file_path, guest_base), parent = Span::current())]
771 pub fn map_file_cow(&mut self, file_path: &Path, guest_base: u64) -> Result<u64> {
772 if self.poisoned {
773 return Err(crate::HyperlightError::PoisonedSandbox);
774 }
775
776 let mut prepared = prepare_file_cow(file_path, guest_base)?;
778
779 let shared_size = self.mem_mgr.shared_mem.mem_size() as u64;
782 let base_addr = crate::mem::layout::SandboxMemoryLayout::BASE_ADDRESS as u64;
783 let shared_end = base_addr.checked_add(shared_size).ok_or_else(|| {
784 crate::HyperlightError::Error("shared memory end overflow".to_string())
785 })?;
786 let mapping_end = guest_base
787 .checked_add(prepared.size as u64)
788 .ok_or_else(|| {
789 crate::HyperlightError::Error(format!(
790 "map_file_cow: guest address overflow: {:#x} + {:#x}",
791 guest_base, prepared.size
792 ))
793 })?;
794 if guest_base < shared_end && mapping_end > base_addr {
795 return Err(crate::HyperlightError::Error(format!(
796 "map_file_cow: mapping [{:#x}..{:#x}) overlaps sandbox shared memory [{:#x}..{:#x})",
797 guest_base, mapping_end, base_addr, shared_end,
798 )));
799 }
800
801 let region = prepared.to_memory_region()?;
803
804 unsafe { self.vm.map_region(®ion) }
805 .map_err(HyperlightVmError::MapRegion)
806 .map_err(crate::HyperlightError::HyperlightVmError)?;
807
808 self.snapshot = None;
809
810 let size = prepared.size as u64;
811
812 prepared.mark_consumed();
820
821 Ok(size)
822 }
823
824 #[cfg(feature = "fuzzing")]
833 #[instrument(err(Debug), skip(self, args), parent = Span::current())]
834 pub fn call_type_erased_guest_function_by_name(
835 &mut self,
836 func_name: &str,
837 ret_type: ReturnType,
838 args: Vec<ParameterValue>,
839 ) -> Result<ReturnValue> {
840 if self.poisoned {
841 return Err(crate::HyperlightError::PoisonedSandbox);
842 }
843 self.snapshot = None;
845 maybe_time_and_emit_guest_call(func_name, || {
846 self.call_guest_function_by_name_no_reset(func_name, ret_type, args)
847 })
848 }
849
850 fn call_guest_function_by_name_no_reset(
851 &mut self,
852 function_name: &str,
853 return_type: ReturnType,
854 args: Vec<ParameterValue>,
855 ) -> Result<ReturnValue> {
856 if self.poisoned {
857 return Err(crate::HyperlightError::PoisonedSandbox);
858 }
859 self.vm.clear_cancel();
863
864 let res = (|| {
865 let estimated_capacity = estimate_flatbuffer_capacity(function_name, &args);
866
867 let fc = FunctionCall::new(
868 function_name.to_string(),
869 Some(args),
870 FunctionCallType::Guest,
871 return_type,
872 );
873
874 let mut builder = FlatBufferBuilder::with_capacity(estimated_capacity);
875 let buffer = fc.encode(&mut builder);
876
877 self.mem_mgr.write_guest_function_call(buffer)?;
878
879 let dispatch_res = self.vm.dispatch_call_from_host(
880 &mut self.mem_mgr,
881 &self.host_funcs,
882 #[cfg(gdb)]
883 self.dbg_mem_access_fn.clone(),
884 );
885
886 if let Err(e) = dispatch_res {
889 let (error, should_poison) = e.promote();
890 self.poisoned |= should_poison;
891 return Err(error);
892 }
893
894 let guest_result = self.mem_mgr.get_guest_function_call_result()?.into_inner();
895
896 match guest_result {
897 Ok(val) => Ok(val),
898 Err(guest_error) => {
899 metrics::counter!(
900 METRIC_GUEST_ERROR,
901 METRIC_GUEST_ERROR_LABEL_CODE => (guest_error.code as u64).to_string()
902 )
903 .increment(1);
904
905 Err(HyperlightError::GuestError(
906 guest_error.code,
907 guest_error.message,
908 ))
909 }
910 }
911 })();
912
913 self.mem_mgr.abort_buffer.clear();
915
916 if let Err(e) = &res {
922 self.mem_mgr.clear_io_buffers();
923
924 self.poisoned |= e.is_poison_error();
926 }
927
928 res
931 }
932
933 pub fn interrupt_handle(&self) -> Arc<dyn InterruptHandle> {
963 self.vm.interrupt_handle()
964 }
965
966 #[cfg(crashdump)]
1002 #[instrument(err(Debug), skip_all, parent = Span::current())]
1003 pub fn generate_crashdump(&mut self) -> Result<()> {
1004 crate::hypervisor::crashdump::generate_crashdump(&self.vm, &mut self.mem_mgr, None)
1005 }
1006
1007 #[cfg(crashdump)]
1014 #[instrument(err(Debug), skip_all, parent = Span::current())]
1015 pub fn generate_crashdump_to_dir(&mut self, dir: impl Into<String>) -> Result<()> {
1016 crate::hypervisor::crashdump::generate_crashdump(
1017 &self.vm,
1018 &mut self.mem_mgr,
1019 Some(dir.into()),
1020 )
1021 }
1022
1023 pub fn poisoned(&self) -> bool {
1060 self.poisoned
1061 }
1062}
1063
1064impl Callable for MultiUseSandbox {
1065 fn call<Output: SupportedReturnType>(
1066 &mut self,
1067 func_name: &str,
1068 args: impl ParameterTuple,
1069 ) -> Result<Output> {
1070 if self.poisoned {
1071 return Err(crate::HyperlightError::PoisonedSandbox);
1072 }
1073 self.call(func_name, args)
1074 }
1075}
1076
1077impl std::fmt::Debug for MultiUseSandbox {
1078 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1079 f.debug_struct("MultiUseSandbox").finish()
1080 }
1081}
1082
1083fn warn_on_layout_override(
1088 caller: &crate::sandbox::SandboxConfiguration,
1089 snapshot: &crate::mem::layout::SandboxMemoryLayout,
1090) {
1091 let mismatches: &[(&str, u64, u64)] = &[
1092 (
1093 "input_data_size",
1094 caller.get_input_data_size() as u64,
1095 snapshot.input_data_size as u64,
1096 ),
1097 (
1098 "output_data_size",
1099 caller.get_output_data_size() as u64,
1100 snapshot.output_data_size as u64,
1101 ),
1102 (
1103 "heap_size",
1104 caller.get_heap_size(),
1105 snapshot.heap_size as u64,
1106 ),
1107 (
1108 "scratch_size",
1109 caller.get_scratch_size() as u64,
1110 snapshot.get_scratch_size() as u64,
1111 ),
1112 ];
1113 for (name, supplied, snap) in mismatches {
1114 if supplied != snap {
1115 tracing::warn!(
1116 "from_snapshot ignoring caller-supplied {} ({}); using snapshot value ({})",
1117 name,
1118 supplied,
1119 snap
1120 );
1121 }
1122 }
1123}
1124
1125#[cfg(test)]
1126mod tests {
1127 use std::sync::{Arc, Barrier};
1128 use std::thread;
1129
1130 use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
1131 use hyperlight_testing::sandbox_sizes::{LARGE_HEAP_SIZE, MEDIUM_HEAP_SIZE, SMALL_HEAP_SIZE};
1132 use hyperlight_testing::simple_guest_as_string;
1133
1134 use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags, MemoryRegionType};
1135 use crate::mem::shared_mem::{ExclusiveSharedMemory, GuestSharedMemory, SharedMemory as _};
1136 use crate::sandbox::SandboxConfiguration;
1137 use crate::{GuestBinary, HyperlightError, MultiUseSandbox, Result, UninitializedSandbox};
1138
1139 #[test]
1140 fn poison() {
1141 let mut sbox: MultiUseSandbox = {
1142 let path = simple_guest_as_string().unwrap();
1143 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
1144 u_sbox.evolve()
1145 }
1146 .unwrap();
1147 let snapshot = sbox.snapshot().unwrap();
1148
1149 let res = sbox
1151 .call::<()>("guest_panic", "hello".to_string())
1152 .unwrap_err();
1153 assert!(
1154 matches!(res, HyperlightError::GuestAborted(code, context) if code == ErrorCode::UnknownError as u8 && context.contains("hello"))
1155 );
1156 assert!(sbox.poisoned());
1157
1158 let res = sbox
1160 .call::<()>("guest_panic", "hello2".to_string())
1161 .unwrap_err();
1162 assert!(matches!(res, HyperlightError::PoisonedSandbox));
1163
1164 if let Err(e) = sbox.snapshot() {
1166 assert!(sbox.poisoned());
1167 assert!(matches!(e, HyperlightError::PoisonedSandbox));
1168 } else {
1169 panic!("Snapshot should fail");
1170 }
1171
1172 {
1174 let map_mem = allocate_guest_memory();
1175 let guest_base = 0x0;
1176 let region = region_for_memory(&map_mem, guest_base, MemoryRegionFlags::READ);
1177 let res = unsafe { sbox.map_region(®ion) }.unwrap_err();
1178 assert!(matches!(res, HyperlightError::PoisonedSandbox));
1179 }
1180
1181 {
1183 let temp_file = std::env::temp_dir().join("test_poison_map_file.bin");
1184 let res = sbox.map_file_cow(&temp_file, 0x0).unwrap_err();
1185 assert!(matches!(res, HyperlightError::PoisonedSandbox));
1186 std::fs::remove_file(&temp_file).ok(); }
1188
1189 #[allow(deprecated)]
1191 let res = sbox
1192 .call_guest_function_by_name::<String>("Echo", "test".to_string())
1193 .unwrap_err();
1194 assert!(matches!(res, HyperlightError::PoisonedSandbox));
1195
1196 sbox.restore(snapshot.clone()).unwrap();
1198 assert!(!sbox.poisoned());
1199
1200 let res = sbox.call::<String>("Echo", "hello2".to_string()).unwrap();
1202 assert_eq!(res, "hello2".to_string());
1203 assert!(!sbox.poisoned());
1204
1205 let res = sbox
1207 .call::<()>("guest_panic", "hello".to_string())
1208 .unwrap_err();
1209 assert!(
1210 matches!(res, HyperlightError::GuestAborted(code, context) if code == ErrorCode::UnknownError as u8 && context.contains("hello"))
1211 );
1212 assert!(sbox.poisoned());
1213
1214 sbox.restore(snapshot.clone()).unwrap();
1216 assert!(!sbox.poisoned());
1217
1218 let res = sbox.call::<String>("Echo", "hello3".to_string()).unwrap();
1220 assert_eq!(res, "hello3".to_string());
1221 assert!(!sbox.poisoned());
1222
1223 let _ = sbox.snapshot().unwrap();
1225 }
1226
1227 #[test]
1229 fn host_func_error() {
1230 let path = simple_guest_as_string().unwrap();
1231 let mut sandbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
1232 sandbox
1233 .register("HostError", || -> Result<()> {
1234 Err(HyperlightError::Error("hi".to_string()))
1235 })
1236 .unwrap();
1237 let mut sandbox = sandbox.evolve().unwrap();
1238
1239 for _ in 0..1000 {
1241 let result = sandbox
1242 .call::<i64>(
1243 "CallGivenParamlessHostFuncThatReturnsI64",
1244 "HostError".to_string(),
1245 )
1246 .unwrap_err();
1247
1248 assert!(
1249 matches!(result, HyperlightError::GuestError(code, msg) if code == ErrorCode::HostFunctionError && msg == "hi"),
1250 );
1251 }
1252 }
1253
1254 #[test]
1255 fn call_host_func_expect_error() {
1256 let path = simple_guest_as_string().unwrap();
1257 let sandbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
1258 let mut sandbox = sandbox.evolve().unwrap();
1259 sandbox
1260 .call::<()>("CallHostExpectError", "SomeUnknownHostFunc".to_string())
1261 .unwrap();
1262 }
1263
1264 #[test]
1266 fn io_buffer_reset() {
1267 let mut cfg = SandboxConfiguration::default();
1268 cfg.set_input_data_size(4096);
1269 cfg.set_output_data_size(4096);
1270 let path = simple_guest_as_string().unwrap();
1271 let mut sandbox =
1272 UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg)).unwrap();
1273 sandbox.register("HostAdd", |a: i32, b: i32| a + b).unwrap();
1274 let mut sandbox = sandbox.evolve().unwrap();
1275
1276 for _ in 0..1000 {
1278 let result = sandbox.call::<i32>("Add", (5i32, 10i32)).unwrap();
1279 assert_eq!(result, 15);
1280 let result = sandbox.call::<i32>("AddToStaticAndFail", ()).unwrap_err();
1281 assert!(
1282 matches!(result, HyperlightError::GuestError (code, msg ) if code == ErrorCode::GuestError && msg == "Crash on purpose")
1283 );
1284 }
1285 }
1286
1287 #[test]
1289 fn test_call_guest_function_by_name() {
1290 let mut sbox: MultiUseSandbox = {
1291 let path = simple_guest_as_string().unwrap();
1292 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
1293 u_sbox.evolve()
1294 }
1295 .unwrap();
1296
1297 let snapshot = sbox.snapshot().unwrap();
1298
1299 let _ = sbox.call::<i32>("AddToStatic", 5i32).unwrap();
1300 let res: i32 = sbox.call("GetStatic", ()).unwrap();
1301 assert_eq!(res, 5);
1302
1303 sbox.restore(snapshot).unwrap();
1304 #[allow(deprecated)]
1305 let _ = sbox
1306 .call_guest_function_by_name::<i32>("AddToStatic", 5i32)
1307 .unwrap();
1308 #[allow(deprecated)]
1309 let res: i32 = sbox.call_guest_function_by_name("GetStatic", ()).unwrap();
1310 assert_eq!(res, 0);
1311 }
1312
1313 #[test]
1316 fn test_with_small_stack_and_heap() {
1317 let mut cfg = SandboxConfiguration::default();
1318 cfg.set_heap_size(20 * 1024);
1319 let min_scratch = hyperlight_common::layout::min_scratch_size(
1324 cfg.get_input_data_size(),
1325 cfg.get_output_data_size(),
1326 );
1327 cfg.set_scratch_size(min_scratch + 0x10000 + 0x10000);
1328
1329 let mut sbox1: MultiUseSandbox = {
1330 let path = simple_guest_as_string().unwrap();
1331 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg)).unwrap();
1332 u_sbox.evolve()
1333 }
1334 .unwrap();
1335
1336 for _ in 0..1000 {
1337 sbox1.call::<String>("Echo", "hello".to_string()).unwrap();
1338 }
1339
1340 let mut sbox2: MultiUseSandbox = {
1341 let path = simple_guest_as_string().unwrap();
1342 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg)).unwrap();
1343 u_sbox.evolve()
1344 }
1345 .unwrap();
1346
1347 for i in 0..1000 {
1348 sbox2
1349 .call::<i32>(
1350 "PrintUsingPrintf",
1351 format!("Hello World {}\n", i).to_string(),
1352 )
1353 .unwrap();
1354 }
1355 }
1356
1357 #[test]
1360 fn snapshot_evolve_restore_handles_state_correctly() {
1361 let mut sbox: MultiUseSandbox = {
1362 let path = simple_guest_as_string().unwrap();
1363 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
1364 u_sbox.evolve()
1365 }
1366 .unwrap();
1367
1368 let snapshot = sbox.snapshot().unwrap();
1369
1370 let _ = sbox.call::<i32>("AddToStatic", 5i32).unwrap();
1371
1372 let res: i32 = sbox.call("GetStatic", ()).unwrap();
1373 assert_eq!(res, 5);
1374
1375 sbox.restore(snapshot).unwrap();
1376 let res: i32 = sbox.call("GetStatic", ()).unwrap();
1377 assert_eq!(res, 0);
1378 }
1379
1380 #[test]
1381 fn test_trigger_exception_on_guest() {
1382 let usbox = UninitializedSandbox::new(
1383 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
1384 None,
1385 )
1386 .unwrap();
1387
1388 let mut multi_use_sandbox: MultiUseSandbox = usbox.evolve().unwrap();
1389
1390 let res: Result<()> = multi_use_sandbox.call("TriggerException", ());
1391
1392 assert!(res.is_err());
1393
1394 match res.unwrap_err() {
1395 HyperlightError::GuestAborted(_, msg) => {
1396 #[cfg(target_arch = "x86_64")]
1398 assert!(msg.contains("InvalidOpcode"));
1399 #[cfg(target_arch = "aarch64")]
1400 assert!(msg.contains("0x2000000"));
1401 }
1402 e => panic!("Expected HyperlightError::GuestAborted but got {:?}", e),
1403 }
1404 }
1405
1406 #[test]
1407 fn create_200_sandboxes() {
1408 const NUM_THREADS: usize = 10;
1409 const SANDBOXES_PER_THREAD: usize = 20;
1410
1411 let start_barrier = Arc::new(Barrier::new(NUM_THREADS + 1));
1413 let mut thread_handles = vec![];
1414
1415 for _ in 0..NUM_THREADS {
1416 let barrier = start_barrier.clone();
1417
1418 let handle = thread::spawn(move || {
1419 barrier.wait();
1420
1421 for _ in 0..SANDBOXES_PER_THREAD {
1422 let guest_path = simple_guest_as_string().expect("Guest Binary Missing");
1423 let uninit =
1424 UninitializedSandbox::new(GuestBinary::FilePath(guest_path), None).unwrap();
1425
1426 let mut sandbox: MultiUseSandbox = uninit.evolve().unwrap();
1427
1428 let result: i32 = sandbox.call("GetStatic", ()).unwrap();
1429 assert_eq!(result, 0);
1430 }
1431 });
1432
1433 thread_handles.push(handle);
1434 }
1435
1436 start_barrier.wait();
1437
1438 for handle in thread_handles {
1439 handle.join().unwrap();
1440 }
1441 }
1442
1443 #[test]
1444 fn test_mmap() {
1445 let mut sbox = UninitializedSandbox::new(
1446 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
1447 None,
1448 )
1449 .unwrap()
1450 .evolve()
1451 .unwrap();
1452
1453 let expected = b"hello world";
1454 let map_mem = page_aligned_memory(expected);
1455 let guest_base = 0x1_0000_0000; unsafe {
1458 sbox.map_region(®ion_for_memory(
1459 &map_mem,
1460 guest_base,
1461 MemoryRegionFlags::READ,
1462 ))
1463 .unwrap();
1464 }
1465
1466 let _guard = map_mem.lock.try_read().unwrap();
1467 let actual: Vec<u8> = sbox
1468 .call(
1469 "ReadMappedBuffer",
1470 (guest_base as u64, expected.len() as u64, true),
1471 )
1472 .unwrap();
1473
1474 assert_eq!(actual, expected);
1475 }
1476
1477 #[test]
1479 fn test_mmap_write_exec() {
1480 let mut sbox = UninitializedSandbox::new(
1481 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
1482 None,
1483 )
1484 .unwrap()
1485 .evolve()
1486 .unwrap();
1487
1488 #[cfg(target_arch = "x86_64")]
1489 let expected = &[0x90, 0x90, 0x90, 0xC3]; #[cfg(target_arch = "aarch64")]
1491 let expected = &[0x1f, 0x20, 0x03, 0xd5, 0xc0, 0x03, 0x5f, 0xd6];
1492 let map_mem = page_aligned_memory(expected);
1493 let guest_base = 0x1_0000_0000; unsafe {
1496 sbox.map_region(®ion_for_memory(
1497 &map_mem,
1498 guest_base,
1499 MemoryRegionFlags::READ | MemoryRegionFlags::EXECUTE,
1500 ))
1501 .unwrap();
1502 }
1503
1504 let _guard = map_mem.lock.try_read().unwrap();
1505
1506 let succeed = sbox
1508 .call::<bool>(
1509 "ExecMappedBuffer",
1510 (guest_base as u64, expected.len() as u64),
1511 )
1512 .unwrap();
1513 assert!(succeed, "Expected execution of mapped buffer to succeed");
1514
1515 let err = sbox
1517 .call::<bool>(
1518 "WriteMappedBuffer",
1519 (guest_base as u64, expected.len() as u64),
1520 )
1521 .unwrap_err();
1522
1523 match err {
1524 HyperlightError::MemoryAccessViolation(addr, ..) if addr == guest_base as u64 => {}
1525 _ => panic!("Expected MemoryAccessViolation error"),
1526 };
1527 }
1528
1529 fn page_aligned_memory(src: &[u8]) -> GuestSharedMemory {
1530 use hyperlight_common::mem::PAGE_SIZE_USIZE;
1531
1532 let len = src.len().div_ceil(PAGE_SIZE_USIZE) * PAGE_SIZE_USIZE;
1533
1534 let mut mem = ExclusiveSharedMemory::new(len).unwrap();
1535 mem.copy_from_slice(src, 0).unwrap();
1536
1537 let (_, guest_mem) = mem.build();
1538
1539 guest_mem
1540 }
1541
1542 fn region_for_memory(
1543 mem: &GuestSharedMemory,
1544 guest_base: usize,
1545 flags: MemoryRegionFlags,
1546 ) -> MemoryRegion {
1547 let len = mem.mem_size();
1548 MemoryRegion {
1549 host_region: mem.host_region_base()..mem.host_region_end(),
1550 guest_region: guest_base..(guest_base + len),
1551 flags,
1552 region_type: MemoryRegionType::Heap,
1553 }
1554 }
1555
1556 fn allocate_guest_memory() -> GuestSharedMemory {
1557 page_aligned_memory(b"test data for snapshot")
1558 }
1559
1560 #[test]
1561 fn snapshot_restore_handles_remapping_correctly() {
1562 let mut sbox: MultiUseSandbox = {
1563 let path = simple_guest_as_string().unwrap();
1564 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
1565 u_sbox.evolve().unwrap()
1566 };
1567
1568 let snapshot1 = sbox.snapshot().unwrap();
1570 assert_eq!(sbox.vm.get_mapped_regions().count(), 0);
1571
1572 let map_mem = allocate_guest_memory();
1574 let guest_base = 0x200000000_usize;
1575 let region = region_for_memory(&map_mem, guest_base, MemoryRegionFlags::READ);
1576
1577 unsafe { sbox.map_region(®ion).unwrap() };
1578 assert_eq!(sbox.vm.get_mapped_regions().count(), 1);
1579 let orig_read = sbox
1580 .call::<Vec<u8>>(
1581 "ReadMappedBuffer",
1582 (
1583 guest_base as u64,
1584 hyperlight_common::vmem::PAGE_SIZE as u64,
1585 true,
1586 ),
1587 )
1588 .unwrap();
1589
1590 let snapshot2 = sbox.snapshot().unwrap();
1592 assert_eq!(sbox.vm.get_mapped_regions().count(), 1);
1593
1594 sbox.restore(snapshot1.clone()).unwrap();
1596 assert_eq!(sbox.vm.get_mapped_regions().count(), 0);
1597 let is_mapped = sbox
1598 .call::<bool>("CheckMapped", (guest_base as u64,))
1599 .unwrap();
1600 assert!(!is_mapped);
1601
1602 sbox.restore(snapshot2.clone()).unwrap();
1605 assert_eq!(sbox.vm.get_mapped_regions().count(), 0);
1606 let is_mapped = sbox
1607 .call::<bool>("CheckMapped", (guest_base as u64,))
1608 .unwrap();
1609 assert!(is_mapped);
1610
1611 let new_read = sbox
1613 .call::<Vec<u8>>(
1614 "ReadMappedBuffer",
1615 (
1616 guest_base as u64,
1617 hyperlight_common::vmem::PAGE_SIZE as u64,
1618 false,
1619 ),
1620 )
1621 .unwrap();
1622 assert_eq!(new_read, orig_read);
1623 }
1624
1625 #[test]
1629 fn snapshot_restore_across_sandboxes_preserves_mapped_region_contents() {
1630 let mut source: MultiUseSandbox = {
1631 let path = simple_guest_as_string().unwrap();
1632 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
1633 u_sbox.evolve().unwrap()
1634 };
1635
1636 let map_mem = allocate_guest_memory();
1637 let guest_base = 0x200000000_usize;
1638 let region = region_for_memory(&map_mem, guest_base, MemoryRegionFlags::READ);
1639 unsafe { source.map_region(®ion).unwrap() };
1640
1641 let orig_read = source
1643 .call::<Vec<u8>>(
1644 "ReadMappedBuffer",
1645 (
1646 guest_base as u64,
1647 hyperlight_common::vmem::PAGE_SIZE as u64,
1648 true,
1649 ),
1650 )
1651 .unwrap();
1652
1653 let snapshot = source.snapshot().unwrap();
1654
1655 let mut target: MultiUseSandbox = {
1656 let path = simple_guest_as_string().unwrap();
1657 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
1658 u_sbox.evolve().unwrap()
1659 };
1660 assert_eq!(target.vm.get_mapped_regions().count(), 0);
1661
1662 target.restore(snapshot).unwrap();
1663 assert_eq!(target.vm.get_mapped_regions().count(), 0);
1664
1665 let new_read = target
1668 .call::<Vec<u8>>(
1669 "ReadMappedBuffer",
1670 (
1671 guest_base as u64,
1672 hyperlight_common::vmem::PAGE_SIZE as u64,
1673 false,
1674 ),
1675 )
1676 .unwrap();
1677 assert_eq!(new_read, orig_read);
1678 }
1679
1680 #[test]
1681 fn snapshot_restore_across_sandboxes() {
1682 let mut sandbox = {
1683 let path = simple_guest_as_string().unwrap();
1684 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
1685 u_sbox.evolve().unwrap()
1686 };
1687
1688 let mut sandbox2 = {
1689 let path = simple_guest_as_string().unwrap();
1690 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
1691 u_sbox.evolve().unwrap()
1692 };
1693
1694 sandbox.call::<i32>("AddToStatic", 42i32).unwrap();
1695 assert_eq!(sandbox2.call::<i32>("GetStatic", ()).unwrap(), 0);
1696
1697 let snapshot = sandbox.snapshot().unwrap();
1698 sandbox2.restore(snapshot).unwrap();
1699 assert_eq!(sandbox2.call::<i32>("GetStatic", ()).unwrap(), 42);
1700 }
1701
1702 #[test]
1703 fn snapshot_restore_rejects_incompatible_layout() {
1704 let mut sandbox = {
1705 let path = simple_guest_as_string().unwrap();
1706 let mut cfg = SandboxConfiguration::default();
1707 cfg.set_heap_size(0x10_000);
1708 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg)).unwrap();
1709 u_sbox.evolve().unwrap()
1710 };
1711
1712 let mut sandbox2 = {
1713 let path = simple_guest_as_string().unwrap();
1714 let mut cfg = SandboxConfiguration::default();
1715 cfg.set_heap_size(0x20_000);
1716 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg)).unwrap();
1717 u_sbox.evolve().unwrap()
1718 };
1719
1720 let snapshot = sandbox.snapshot().unwrap();
1721 let err = sandbox2.restore(snapshot);
1722 assert!(matches!(err, Err(HyperlightError::SnapshotLayoutMismatch)));
1723 }
1724
1725 #[test]
1728 fn snapshot_restore_failure_leaves_target_usable() {
1729 let path = simple_guest_as_string().unwrap();
1730 let mut cfg_a = SandboxConfiguration::default();
1731 cfg_a.set_heap_size(0x10_000);
1732 let mut source = UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg_a))
1733 .unwrap()
1734 .evolve()
1735 .unwrap();
1736
1737 let path = simple_guest_as_string().unwrap();
1738 let mut cfg_b = SandboxConfiguration::default();
1739 cfg_b.set_heap_size(0x20_000);
1740 let mut target = UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg_b))
1741 .unwrap()
1742 .evolve()
1743 .unwrap();
1744
1745 target.call::<i32>("AddToStatic", 5i32).unwrap();
1746 let bad_snapshot = source.snapshot().unwrap();
1747 let err = target.restore(bad_snapshot);
1748 assert!(matches!(err, Err(HyperlightError::SnapshotLayoutMismatch)));
1749
1750 assert_eq!(target.call::<i32>("GetStatic", ()).unwrap(), 5);
1751 target.call::<i32>("AddToStatic", 3i32).unwrap();
1752 assert_eq!(target.call::<i32>("GetStatic", ()).unwrap(), 8);
1753
1754 let good_snapshot = target.snapshot().unwrap();
1755 target.call::<i32>("AddToStatic", 100i32).unwrap();
1756 assert_eq!(target.call::<i32>("GetStatic", ()).unwrap(), 108);
1757 target.restore(good_snapshot).unwrap();
1758 assert_eq!(target.call::<i32>("GetStatic", ()).unwrap(), 8);
1759 }
1760
1761 #[test]
1764 fn snapshot_restore_across_sandboxes_target_has_mapped_regions() {
1765 let mut source: MultiUseSandbox = {
1766 let path = simple_guest_as_string().unwrap();
1767 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
1768 u_sbox.evolve().unwrap()
1769 };
1770 source.call::<i32>("AddToStatic", 23i32).unwrap();
1771 let snapshot = source.snapshot().unwrap();
1772
1773 let mut target: MultiUseSandbox = {
1774 let path = simple_guest_as_string().unwrap();
1775 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
1776 u_sbox.evolve().unwrap()
1777 };
1778 let map_mem = allocate_guest_memory();
1779 let guest_base = 0x200000000_usize;
1780 let region = region_for_memory(&map_mem, guest_base, MemoryRegionFlags::READ);
1781 unsafe { target.map_region(®ion).unwrap() };
1782 assert_eq!(target.vm.get_mapped_regions().count(), 1);
1783
1784 target.restore(snapshot).unwrap();
1785 assert_eq!(target.vm.get_mapped_regions().count(), 0);
1786 assert_eq!(target.call::<i32>("GetStatic", ()).unwrap(), 23);
1787 }
1788
1789 #[test]
1793 fn snapshot_restore_across_sandboxes_both_have_different_mapped_regions() {
1794 let mut source: MultiUseSandbox = {
1795 let path = simple_guest_as_string().unwrap();
1796 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
1797 u_sbox.evolve().unwrap()
1798 };
1799 let source_mem = allocate_guest_memory();
1800 let source_base = 0x200000000_usize;
1801 let source_region = region_for_memory(&source_mem, source_base, MemoryRegionFlags::READ);
1802 unsafe { source.map_region(&source_region).unwrap() };
1803 let orig_read = source
1804 .call::<Vec<u8>>(
1805 "ReadMappedBuffer",
1806 (
1807 source_base as u64,
1808 hyperlight_common::vmem::PAGE_SIZE as u64,
1809 true,
1810 ),
1811 )
1812 .unwrap();
1813 source.call::<i32>("AddToStatic", 9i32).unwrap();
1814 let snapshot = source.snapshot().unwrap();
1815
1816 let mut target: MultiUseSandbox = {
1817 let path = simple_guest_as_string().unwrap();
1818 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
1819 u_sbox.evolve().unwrap()
1820 };
1821 let target_mem = allocate_guest_memory();
1822 let target_base = 0x300000000_usize;
1823 let target_region = region_for_memory(&target_mem, target_base, MemoryRegionFlags::READ);
1824 unsafe { target.map_region(&target_region).unwrap() };
1825 assert_eq!(target.vm.get_mapped_regions().count(), 1);
1826
1827 target.restore(snapshot).unwrap();
1828
1829 assert_eq!(target.vm.get_mapped_regions().count(), 0);
1830 assert_eq!(target.call::<i32>("GetStatic", ()).unwrap(), 9);
1831
1832 let new_read = target
1833 .call::<Vec<u8>>(
1834 "ReadMappedBuffer",
1835 (
1836 source_base as u64,
1837 hyperlight_common::vmem::PAGE_SIZE as u64,
1838 false,
1839 ),
1840 )
1841 .unwrap();
1842 assert_eq!(new_read, orig_read);
1843 }
1844
1845 #[test]
1847 fn snapshot_restore_across_sandboxes_repeated() {
1848 let mut source: MultiUseSandbox = {
1849 let path = simple_guest_as_string().unwrap();
1850 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
1851 u_sbox.evolve().unwrap()
1852 };
1853 source.call::<i32>("AddToStatic", 7i32).unwrap();
1854 let snapshot = source.snapshot().unwrap();
1855
1856 let mut target: MultiUseSandbox = {
1857 let path = simple_guest_as_string().unwrap();
1858 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
1859 u_sbox.evolve().unwrap()
1860 };
1861
1862 target.restore(snapshot.clone()).unwrap();
1863 assert_eq!(target.call::<i32>("GetStatic", ()).unwrap(), 7);
1864
1865 target.call::<i32>("AddToStatic", 1000i32).unwrap();
1866 assert_eq!(target.call::<i32>("GetStatic", ()).unwrap(), 1007);
1867
1868 target.restore(snapshot).unwrap();
1869 assert_eq!(target.call::<i32>("GetStatic", ()).unwrap(), 7);
1870 }
1871
1872 #[test]
1875 fn snapshot_restore_resets_debug_registers() {
1876 let mut sandbox: MultiUseSandbox = {
1877 let path = simple_guest_as_string().unwrap();
1878 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
1879 u_sbox.evolve().unwrap()
1880 };
1881
1882 let snapshot = sandbox.snapshot().unwrap();
1883
1884 let dr0_initial: u64 = sandbox.call("GetDr0", ()).unwrap();
1886 assert_eq!(dr0_initial, 0, "DR0 should initially be 0");
1887
1888 const DIRTY_VALUE: u64 = 0xFFFF_FEDC_7654_3210;
1891 sandbox.call::<()>("SetDr0", DIRTY_VALUE).unwrap();
1892 let dr0_dirty: u64 = sandbox.call("GetDr0", ()).unwrap();
1893 assert_eq!(
1894 dr0_dirty, DIRTY_VALUE,
1895 "DR0 should be dirty after SetDr0 call"
1896 );
1897
1898 sandbox.restore(snapshot).unwrap();
1900
1901 let dr0_after_restore: u64 = sandbox.call("GetDr0", ()).unwrap();
1902 assert_eq!(
1903 dr0_after_restore, 0,
1904 "DR0 should be 0 after restore (reset_vcpu should have been called)"
1905 );
1906 }
1907
1908 #[test]
1911 fn stale_abort_buffer_does_not_leak_across_calls() {
1912 let mut sbox: MultiUseSandbox = {
1913 let path = simple_guest_as_string().unwrap();
1914 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
1915 u_sbox.evolve().unwrap()
1916 };
1917
1918 sbox.mem_mgr.abort_buffer.extend_from_slice(&[0xAA; 1020]);
1920
1921 let res = sbox.call::<String>("Echo", "hello".to_string());
1922 assert!(
1923 res.is_ok(),
1924 "Expected Ok after stale abort buffer, got: {:?}",
1925 res.unwrap_err()
1926 );
1927
1928 assert!(
1930 sbox.mem_mgr.abort_buffer.is_empty(),
1931 "abort_buffer should be empty after a guest call"
1932 );
1933 }
1934
1935 #[test]
1937 fn test_sandbox_creation_various_sizes() {
1938 let test_cases: [(&str, u64); 3] = [
1939 ("small (8MB heap)", SMALL_HEAP_SIZE),
1940 ("medium (64MB heap)", MEDIUM_HEAP_SIZE),
1941 ("large (256MB heap)", LARGE_HEAP_SIZE),
1942 ];
1943
1944 for (name, heap_size) in test_cases {
1945 let mut cfg = SandboxConfiguration::default();
1946 cfg.set_heap_size(heap_size);
1947 cfg.set_scratch_size(0x100000);
1948
1949 let path = simple_guest_as_string().unwrap();
1950 let sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg))
1951 .unwrap_or_else(|e| panic!("Failed to create {} sandbox: {}", name, e))
1952 .evolve()
1953 .unwrap_or_else(|e| panic!("Failed to evolve {} sandbox: {}", name, e));
1954
1955 drop(sbox);
1956 }
1957 }
1958
1959 #[cfg(feature = "trace_guest")]
1961 fn sandbox_for_gva_tests() -> MultiUseSandbox {
1962 let path = simple_guest_as_string().unwrap();
1963 UninitializedSandbox::new(GuestBinary::FilePath(path), None)
1964 .unwrap()
1965 .evolve()
1966 .unwrap()
1967 }
1968
1969 #[cfg(feature = "trace_guest")]
1973 fn assert_gva_read_matches(sbox: &mut MultiUseSandbox, gva: u64, len: usize) {
1974 let expected: Vec<u8> = sbox
1976 .call("ReadMappedBuffer", (gva, len as u64, true))
1977 .unwrap();
1978 assert_eq!(expected.len(), len);
1979
1980 let root_pt = sbox.vm.get_root_pt().unwrap();
1982 let actual = sbox
1983 .mem_mgr
1984 .read_guest_memory_by_gva(gva, len, root_pt)
1985 .unwrap();
1986
1987 assert_eq!(
1988 actual, expected,
1989 "read_guest_memory_by_gva at GVA {:#x} (len {}) differs from guest ReadMappedBuffer",
1990 gva, len,
1991 );
1992 }
1993
1994 #[test]
1997 #[cfg(feature = "trace_guest")]
1998 fn read_guest_memory_by_gva_single_page() {
1999 let mut sbox = sandbox_for_gva_tests();
2000 let code_gva = sbox.mem_mgr.layout.get_guest_code_address() as u64;
2001 assert_gva_read_matches(&mut sbox, code_gva, 128);
2002 }
2003
2004 #[test]
2007 #[cfg(feature = "trace_guest")]
2008 fn read_guest_memory_by_gva_full_page() {
2009 let mut sbox = sandbox_for_gva_tests();
2010 let code_gva = sbox.mem_mgr.layout.get_guest_code_address() as u64;
2011 assert_gva_read_matches(&mut sbox, code_gva, 4096);
2012 }
2013
2014 #[test]
2017 #[cfg(feature = "trace_guest")]
2018 fn read_guest_memory_by_gva_unaligned_cross_page() {
2019 let mut sbox = sandbox_for_gva_tests();
2020 let code_gva = sbox.mem_mgr.layout.get_guest_code_address() as u64;
2021 let start = code_gva + 4096 - 1;
2024 println!(
2025 "Testing unaligned cross-page read starting at {:#x} spanning 4097 bytes",
2026 start
2027 );
2028 assert_gva_read_matches(&mut sbox, start, 4097);
2029 }
2030
2031 #[test]
2033 #[cfg(feature = "trace_guest")]
2034 fn read_guest_memory_by_gva_two_full_pages() {
2035 let mut sbox = sandbox_for_gva_tests();
2036 let code_gva = sbox.mem_mgr.layout.get_guest_code_address() as u64;
2037 assert_gva_read_matches(&mut sbox, code_gva, 4096 * 2);
2038 }
2039
2040 #[test]
2044 #[cfg(feature = "trace_guest")]
2045 fn read_guest_memory_by_gva_cross_page_boundary() {
2046 let mut sbox = sandbox_for_gva_tests();
2047 let code_gva = sbox.mem_mgr.layout.get_guest_code_address() as u64;
2048 let start = code_gva + 4096 - 100;
2050 assert_gva_read_matches(&mut sbox, start, 200);
2051 }
2052
2053 fn create_test_file(name: &str, content: &[u8]) -> (std::path::PathBuf, Vec<u8>) {
2057 use std::io::Write;
2058
2059 let page_size = page_size::get();
2060 let padded_len = content.len().max(page_size).div_ceil(page_size) * page_size;
2061 let mut padded = vec![0u8; padded_len];
2062 padded[..content.len()].copy_from_slice(content);
2063
2064 let temp_dir = std::env::temp_dir();
2065 let path = temp_dir.join(name);
2066 let _ = std::fs::remove_file(&path); let mut f = std::fs::File::create(&path).unwrap();
2068 f.write_all(&padded).unwrap();
2069 (path, content.to_vec())
2070 }
2071
2072 #[test]
2075 fn test_map_file_cow_basic() {
2076 let expected = b"hello world from map_file_cow";
2077 let (path, expected_bytes) =
2078 create_test_file("hyperlight_test_map_file_cow_basic.bin", expected);
2079
2080 let mut sbox = UninitializedSandbox::new(
2081 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
2082 None,
2083 )
2084 .unwrap()
2085 .evolve()
2086 .unwrap();
2087
2088 let guest_base: u64 = 0x1_0000_0000;
2089 let mapped_size = sbox.map_file_cow(&path, guest_base).unwrap();
2090 assert!(mapped_size > 0, "mapped_size should be positive");
2091 assert!(
2092 mapped_size >= expected.len() as u64,
2093 "mapped_size should be >= file content length"
2094 );
2095
2096 let actual: Vec<u8> = sbox
2098 .call(
2099 "ReadMappedBuffer",
2100 (guest_base, expected_bytes.len() as u64, true),
2101 )
2102 .unwrap();
2103
2104 assert_eq!(
2105 actual, expected_bytes,
2106 "Guest should read back the exact file content"
2107 );
2108
2109 let _ = std::fs::remove_file(&path);
2111 }
2112
2113 #[test]
2116 fn test_map_file_cow_read_only_enforcement() {
2117 let content = &[0xBB; 4096];
2118 let (path, _) = create_test_file("hyperlight_test_map_file_cow_readonly.bin", content);
2119
2120 let mut sbox = UninitializedSandbox::new(
2121 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
2122 None,
2123 )
2124 .unwrap()
2125 .evolve()
2126 .unwrap();
2127
2128 let guest_base: u64 = 0x1_0000_0000;
2129 sbox.map_file_cow(&path, guest_base).unwrap();
2130
2131 let err = sbox
2133 .call::<bool>("WriteMappedBuffer", (guest_base, content.len() as u64))
2134 .unwrap_err();
2135
2136 match err {
2137 HyperlightError::MemoryAccessViolation(addr, ..) if addr == guest_base => {}
2138 _ => panic!(
2139 "Expected MemoryAccessViolation at guest_base, got: {:?}",
2140 err
2141 ),
2142 };
2143
2144 let _ = std::fs::remove_file(&path);
2146 }
2147
2148 #[test]
2151 fn test_map_file_cow_poisoned() {
2152 let (path, _) = create_test_file("hyperlight_test_map_file_cow_poison.bin", &[0xCC; 4096]);
2153
2154 let mut sbox: MultiUseSandbox = {
2155 let path = simple_guest_as_string().unwrap();
2156 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2157 u_sbox.evolve()
2158 }
2159 .unwrap();
2160 let snapshot = sbox.snapshot().unwrap();
2161
2162 let _ = sbox
2164 .call::<()>("guest_panic", "hello".to_string())
2165 .unwrap_err();
2166 assert!(sbox.poisoned());
2167
2168 let err = sbox.map_file_cow(&path, 0x1_0000_0000).unwrap_err();
2170 assert!(matches!(err, HyperlightError::PoisonedSandbox));
2171
2172 sbox.restore(snapshot).unwrap();
2174 assert!(!sbox.poisoned());
2175 let result = sbox.map_file_cow(&path, 0x1_0000_0000);
2176 assert!(result.is_ok());
2177
2178 let _ = std::fs::remove_file(&path);
2179 }
2180
2181 #[test]
2184 fn test_map_file_cow_multi_vm_same_file() {
2185 let expected = b"shared file content across VMs";
2186 let (path, expected_bytes) =
2187 create_test_file("hyperlight_test_map_file_cow_multi_vm.bin", expected);
2188
2189 let guest_base: u64 = 0x1_0000_0000;
2190
2191 let mut sbox1 = UninitializedSandbox::new(
2192 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
2193 None,
2194 )
2195 .unwrap()
2196 .evolve()
2197 .unwrap();
2198
2199 let mut sbox2 = UninitializedSandbox::new(
2200 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
2201 None,
2202 )
2203 .unwrap()
2204 .evolve()
2205 .unwrap();
2206
2207 sbox1.map_file_cow(&path, guest_base).unwrap();
2209 sbox2.map_file_cow(&path, guest_base).unwrap();
2210
2211 let actual1: Vec<u8> = sbox1
2213 .call(
2214 "ReadMappedBuffer",
2215 (guest_base, expected_bytes.len() as u64, true),
2216 )
2217 .unwrap();
2218 let actual2: Vec<u8> = sbox2
2219 .call(
2220 "ReadMappedBuffer",
2221 (guest_base, expected_bytes.len() as u64, true),
2222 )
2223 .unwrap();
2224
2225 assert_eq!(
2226 actual1, expected_bytes,
2227 "Sandbox 1 should read correct content"
2228 );
2229 assert_eq!(
2230 actual2, expected_bytes,
2231 "Sandbox 2 should read correct content"
2232 );
2233
2234 let _ = std::fs::remove_file(&path);
2235 }
2236
2237 #[test]
2240 fn test_map_file_cow_multi_vm_threaded() {
2241 let expected = b"threaded file mapping test data";
2242 let (path, expected_bytes) =
2243 create_test_file("hyperlight_test_map_file_cow_threaded.bin", expected);
2244
2245 const NUM_THREADS: usize = 5;
2246 let path = Arc::new(path);
2247 let expected_bytes = Arc::new(expected_bytes);
2248 let barrier = Arc::new(Barrier::new(NUM_THREADS));
2249 let mut handles = vec![];
2250
2251 for _ in 0..NUM_THREADS {
2252 let path = path.clone();
2253 let expected_bytes = expected_bytes.clone();
2254 let barrier = barrier.clone();
2255
2256 handles.push(thread::spawn(move || {
2257 barrier.wait();
2258
2259 let mut sbox = UninitializedSandbox::new(
2260 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
2261 None,
2262 )
2263 .unwrap()
2264 .evolve()
2265 .unwrap();
2266
2267 let guest_base: u64 = 0x1_0000_0000;
2268 sbox.map_file_cow(&path, guest_base).unwrap();
2269
2270 let actual: Vec<u8> = sbox
2271 .call(
2272 "ReadMappedBuffer",
2273 (guest_base, expected_bytes.len() as u64, true),
2274 )
2275 .unwrap();
2276
2277 assert_eq!(actual, *expected_bytes);
2278 }));
2279 }
2280
2281 for h in handles {
2282 h.join().unwrap();
2283 }
2284
2285 let _ = std::fs::remove_file(&*path);
2286 }
2287
2288 #[test]
2291 #[cfg(target_os = "windows")]
2292 fn test_map_file_cow_cleanup_no_handle_leak() {
2293 let (path, _) = create_test_file("hyperlight_test_map_file_cow_cleanup.bin", &[0xDD; 4096]);
2294
2295 {
2296 let mut sbox = UninitializedSandbox::new(
2297 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
2298 None,
2299 )
2300 .unwrap()
2301 .evolve()
2302 .unwrap();
2303
2304 sbox.map_file_cow(&path, 0x1_0000_0000).unwrap();
2305 }
2307
2308 std::fs::remove_file(&path)
2309 .expect("File should be deletable after sandbox with map_file_cow is dropped");
2310 }
2311
2312 #[test]
2316 fn test_map_file_cow_snapshot_remapping_cycle() {
2317 let expected = b"snapshot remapping cycle test!";
2318 let (path, expected_bytes) =
2319 create_test_file("hyperlight_test_map_file_cow_snapshot_remap.bin", expected);
2320
2321 let mut sbox = UninitializedSandbox::new(
2322 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
2323 None,
2324 )
2325 .unwrap()
2326 .evolve()
2327 .unwrap();
2328
2329 let guest_base: u64 = 0x1_0000_0000;
2330
2331 let snapshot1 = sbox.snapshot().unwrap();
2333
2334 sbox.map_file_cow(&path, guest_base).unwrap();
2336
2337 let actual: Vec<u8> = sbox
2339 .call(
2340 "ReadMappedBuffer",
2341 (guest_base, expected_bytes.len() as u64, true),
2342 )
2343 .unwrap();
2344 assert_eq!(actual, expected_bytes);
2345
2346 let snapshot2 = sbox.snapshot().unwrap();
2348
2349 sbox.restore(snapshot1.clone()).unwrap();
2351 let is_mapped: bool = sbox.call("CheckMapped", (guest_base,)).unwrap();
2352 assert!(
2353 !is_mapped,
2354 "Region should be unmapped after restoring to snapshot₁"
2355 );
2356
2357 sbox.restore(snapshot2).unwrap();
2360 let is_mapped: bool = sbox.call("CheckMapped", (guest_base,)).unwrap();
2361 assert!(
2362 is_mapped,
2363 "Region should be mapped after restoring to snapshot₂"
2364 );
2365 let actual2: Vec<u8> = sbox
2366 .call(
2367 "ReadMappedBuffer",
2368 (guest_base, expected_bytes.len() as u64, false),
2369 )
2370 .unwrap();
2371 assert_eq!(
2372 actual2, expected_bytes,
2373 "Data should be intact after snapshot₂ restore"
2374 );
2375
2376 let _ = std::fs::remove_file(&path);
2377 }
2378
2379 #[test]
2382 fn test_map_file_cow_snapshot_restore() {
2383 let expected = b"snapshot restore basic test!!";
2384 let (path, expected_bytes) =
2385 create_test_file("hyperlight_test_map_file_cow_snap_restore.bin", expected);
2386
2387 let mut sbox = UninitializedSandbox::new(
2388 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
2389 None,
2390 )
2391 .unwrap()
2392 .evolve()
2393 .unwrap();
2394
2395 let guest_base: u64 = 0x1_0000_0000;
2396 sbox.map_file_cow(&path, guest_base).unwrap();
2397
2398 let actual: Vec<u8> = sbox
2400 .call(
2401 "ReadMappedBuffer",
2402 (guest_base, expected_bytes.len() as u64, true),
2403 )
2404 .unwrap();
2405 assert_eq!(actual, expected_bytes);
2406
2407 let snapshot = sbox.snapshot().unwrap();
2409
2410 sbox.restore(snapshot).unwrap();
2412
2413 let actual2: Vec<u8> = sbox
2415 .call(
2416 "ReadMappedBuffer",
2417 (guest_base, expected_bytes.len() as u64, false),
2418 )
2419 .unwrap();
2420 assert_eq!(
2421 actual2, expected_bytes,
2422 "Data should be readable after restore from snapshot"
2423 );
2424
2425 let _ = std::fs::remove_file(&path);
2426 }
2427
2428 #[test]
2432 fn test_map_file_cow_deferred_basic() {
2433 let expected = b"deferred map_file_cow test data";
2434 let (path, expected_bytes) =
2435 create_test_file("hyperlight_test_map_file_cow_deferred.bin", expected);
2436
2437 let guest_base: u64 = 0x1_0000_0000;
2438
2439 let mut u_sbox = UninitializedSandbox::new(
2440 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
2441 None,
2442 )
2443 .unwrap();
2444
2445 let mapped_size = u_sbox.map_file_cow(&path, guest_base).unwrap();
2447 assert!(mapped_size > 0, "mapped_size should be positive");
2448 assert!(
2449 mapped_size >= expected.len() as u64,
2450 "mapped_size should be >= file content length"
2451 );
2452
2453 let mut sbox: MultiUseSandbox = u_sbox.evolve().unwrap();
2455
2456 let actual: Vec<u8> = sbox
2458 .call(
2459 "ReadMappedBuffer",
2460 (guest_base, expected_bytes.len() as u64, true),
2461 )
2462 .unwrap();
2463
2464 assert_eq!(
2465 actual, expected_bytes,
2466 "Guest should read back the exact file content after deferred mapping"
2467 );
2468
2469 let _ = std::fs::remove_file(&path);
2470 }
2471
2472 #[test]
2476 fn test_map_file_cow_deferred_drop_without_evolve() {
2477 let (path, _) = create_test_file(
2478 "hyperlight_test_map_file_cow_deferred_drop.bin",
2479 &[0xAA; 4096],
2480 );
2481
2482 let guest_base: u64 = 0x1_0000_0000;
2483
2484 {
2485 let mut u_sbox = UninitializedSandbox::new(
2486 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
2487 None,
2488 )
2489 .unwrap();
2490
2491 u_sbox.map_file_cow(&path, guest_base).unwrap();
2492 }
2495
2496 #[cfg(target_os = "windows")]
2499 std::fs::remove_file(&path)
2500 .expect("File should be deletable after dropping UninitializedSandbox");
2501 #[cfg(not(target_os = "windows"))]
2502 let _ = std::fs::remove_file(&path);
2503 }
2504
2505 #[test]
2508 fn test_map_file_cow_unaligned_guest_base() {
2509 let (path, _) =
2510 create_test_file("hyperlight_test_map_file_cow_unaligned.bin", &[0xBB; 4096]);
2511
2512 let mut u_sbox = UninitializedSandbox::new(
2513 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
2514 None,
2515 )
2516 .unwrap();
2517
2518 let unaligned_base: u64 = (page_size::get() + 1) as u64;
2520 let result = u_sbox.map_file_cow(&path, unaligned_base);
2521 assert!(
2522 result.is_err(),
2523 "map_file_cow should reject unaligned guest_base"
2524 );
2525
2526 let _ = std::fs::remove_file(&path);
2527 }
2528
2529 #[test]
2531 fn test_map_file_cow_empty_file() {
2532 let temp_dir = std::env::temp_dir();
2533 let path = temp_dir.join("hyperlight_test_map_file_cow_empty.bin");
2534 let _ = std::fs::remove_file(&path);
2535 std::fs::File::create(&path).unwrap(); let mut u_sbox = UninitializedSandbox::new(
2538 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
2539 None,
2540 )
2541 .unwrap();
2542
2543 let guest_base: u64 = 0x1_0000_0000;
2544 let result = u_sbox.map_file_cow(&path, guest_base);
2545 assert!(result.is_err(), "map_file_cow should reject empty files");
2546
2547 let _ = std::fs::remove_file(&path);
2548 }
2549
2550 #[test]
2552 fn test_map_file_cow_overlapping_mappings() {
2553 let (path1, _) =
2554 create_test_file("hyperlight_test_map_file_cow_overlap1.bin", &[0xAA; 4096]);
2555 let (path2, _) =
2556 create_test_file("hyperlight_test_map_file_cow_overlap2.bin", &[0xBB; 4096]);
2557
2558 let guest_base: u64 = 0x1_0000_0000;
2559
2560 let mut u_sbox = UninitializedSandbox::new(
2561 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
2562 None,
2563 )
2564 .unwrap();
2565
2566 u_sbox.map_file_cow(&path1, guest_base).unwrap();
2568
2569 let result = u_sbox.map_file_cow(&path2, guest_base);
2571 assert!(
2572 result.is_err(),
2573 "map_file_cow should reject overlapping guest address ranges"
2574 );
2575
2576 let _ = std::fs::remove_file(&path1);
2577 let _ = std::fs::remove_file(&path2);
2578 }
2579
2580 #[test]
2583 fn test_map_file_cow_shared_mem_overlap() {
2584 let (path, _) = create_test_file(
2585 "hyperlight_test_map_file_cow_overlap_shm.bin",
2586 &[0xCC; 4096],
2587 );
2588
2589 let mut u_sbox = UninitializedSandbox::new(
2590 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
2591 None,
2592 )
2593 .unwrap();
2594
2595 let base_addr = crate::mem::layout::SandboxMemoryLayout::BASE_ADDRESS as u64;
2597 let result = u_sbox.map_file_cow(&path, base_addr);
2599 assert!(
2600 result.is_err(),
2601 "map_file_cow should reject guest_base inside shared memory"
2602 );
2603
2604 let _ = std::fs::remove_file(&path);
2605 }
2606
2607 #[test]
2608 fn map_region_rejects_overlapping_regions() {
2609 let mut sbox: MultiUseSandbox = {
2610 let path = simple_guest_as_string().unwrap();
2611 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2612 u_sbox.evolve().unwrap()
2613 };
2614
2615 let mem1 = allocate_guest_memory();
2616 let mem2 = allocate_guest_memory();
2617 let guest_base: usize = 0x200000000;
2618 let region1 = region_for_memory(&mem1, guest_base, MemoryRegionFlags::READ);
2619
2620 unsafe { sbox.map_region(®ion1).unwrap() };
2622
2623 let region2 = region_for_memory(&mem2, guest_base, MemoryRegionFlags::READ);
2625 let err = unsafe { sbox.map_region(®ion2) }.unwrap_err();
2626 assert!(
2627 format!("{err:?}").contains("Overlapping"),
2628 "Expected Overlapping error, got: {err:?}"
2629 );
2630 }
2631
2632 #[test]
2633 fn map_region_rejects_partial_overlap() {
2634 let mut sbox: MultiUseSandbox = {
2635 let path = simple_guest_as_string().unwrap();
2636 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2637 u_sbox.evolve().unwrap()
2638 };
2639
2640 let mem1 = page_aligned_memory(&[0xAA; 8192]); let mem2 = page_aligned_memory(&[0xBB; 8192]); let guest_base: usize = 0x200000000;
2644 let region1 = region_for_memory(&mem1, guest_base, MemoryRegionFlags::READ);
2645
2646 unsafe { sbox.map_region(®ion1).unwrap() };
2647
2648 let overlap_base = guest_base - 0x1000;
2650 let region2 = region_for_memory(&mem2, overlap_base, MemoryRegionFlags::READ);
2651 let err = unsafe { sbox.map_region(®ion2) }.unwrap_err();
2652 assert!(
2653 format!("{err:?}").contains("verlap"),
2654 "Expected overlap error for partial overlap, got: {err:?}"
2655 );
2656 }
2657
2658 #[test]
2659 fn map_region_allows_adjacent_non_overlapping() {
2660 let mut sbox: MultiUseSandbox = {
2661 let path = simple_guest_as_string().unwrap();
2662 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2663 u_sbox.evolve().unwrap()
2664 };
2665
2666 let mem1 = allocate_guest_memory();
2667 let mem2 = allocate_guest_memory();
2668 let guest_base: usize = 0x200000000;
2669 let region1 = region_for_memory(&mem1, guest_base, MemoryRegionFlags::READ);
2670 let region_size = mem1.mem_size();
2671
2672 unsafe { sbox.map_region(®ion1).unwrap() };
2673
2674 let adjacent_base = guest_base + region_size;
2676 let region2 = region_for_memory(&mem2, adjacent_base, MemoryRegionFlags::READ);
2677 unsafe { sbox.map_region(®ion2).unwrap() };
2678 }
2679
2680 #[test]
2681 fn map_region_rejects_overlap_with_snapshot() {
2682 let mut sbox: MultiUseSandbox = {
2683 let path = simple_guest_as_string().unwrap();
2684 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2685 u_sbox.evolve().unwrap()
2686 };
2687
2688 let mem = allocate_guest_memory();
2690 let region = region_for_memory(
2691 &mem,
2692 crate::mem::layout::SandboxMemoryLayout::BASE_ADDRESS,
2693 MemoryRegionFlags::READ,
2694 );
2695 let err = unsafe { sbox.map_region(®ion) }.unwrap_err();
2696 assert!(
2697 format!("{err:?}").contains("Overlapping"),
2698 "Expected Overlapping error for snapshot overlap, got: {err:?}"
2699 );
2700 }
2701
2702 #[test]
2703 fn map_region_rejects_overlap_with_scratch() {
2704 let mut sbox: MultiUseSandbox = {
2705 let path = simple_guest_as_string().unwrap();
2706 let u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2707 u_sbox.evolve().unwrap()
2708 };
2709
2710 let scratch_addr = hyperlight_common::layout::scratch_base_gpa(
2712 crate::sandbox::SandboxConfiguration::DEFAULT_SCRATCH_SIZE,
2713 ) as usize;
2714 let mem = allocate_guest_memory();
2715 let region = region_for_memory(&mem, scratch_addr, MemoryRegionFlags::READ);
2716 let err = unsafe { sbox.map_region(®ion) }.unwrap_err();
2717 assert!(
2718 format!("{err:?}").contains("verlap"),
2719 "Expected overlap error for scratch region, got: {err:?}"
2720 );
2721 }
2722
2723 mod from_snapshot {
2725 use std::sync::Arc;
2726
2727 use hyperlight_testing::simple_guest_as_string;
2728
2729 use crate::func::Registerable;
2730 use crate::sandbox::SandboxConfiguration;
2731 use crate::sandbox::snapshot::Snapshot;
2732 use crate::{
2733 GuestBinary, HostFunctions, HyperlightError, MultiUseSandbox, UninitializedSandbox,
2734 };
2735
2736 fn make_sandbox() -> MultiUseSandbox {
2737 let path = simple_guest_as_string().unwrap();
2738 UninitializedSandbox::new(GuestBinary::FilePath(path), None)
2739 .unwrap()
2740 .evolve()
2741 .unwrap()
2742 }
2743
2744 fn make_sandbox_with_add() -> MultiUseSandbox {
2746 let path = simple_guest_as_string().unwrap();
2747 let mut u = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2748 u.register_host_function("Add", |a: i32, b: i32| Ok(a + b))
2749 .unwrap();
2750 u.evolve().unwrap()
2751 }
2752
2753 fn host_funcs_with_matching_add() -> HostFunctions {
2754 let mut hf = HostFunctions::default();
2755 hf.register_host_function("Add", |a: i32, b: i32| Ok(a + b))
2756 .unwrap();
2757 hf
2758 }
2759
2760 #[test]
2761 fn round_trip_running_sandbox() {
2762 let mut sbox = make_sandbox();
2763 sbox.call::<i32>("AddToStatic", 11i32).unwrap();
2764 let snapshot = sbox.snapshot().unwrap();
2765 let mut sbox2 =
2766 MultiUseSandbox::from_snapshot(snapshot, HostFunctions::default(), None).unwrap();
2767 assert_eq!(sbox2.call::<i32>("GetStatic", ()).unwrap(), 11);
2768 let echoed: String = sbox2.call("Echo", "hi".to_string()).unwrap();
2769 assert_eq!(echoed, "hi");
2770 }
2771
2772 #[test]
2773 fn round_trip_pre_init_snapshot() {
2774 let path = simple_guest_as_string().unwrap();
2775 let snap =
2776 Snapshot::from_env(GuestBinary::FilePath(path), SandboxConfiguration::default())
2777 .unwrap();
2778 let mut sbox =
2779 MultiUseSandbox::from_snapshot(Arc::new(snap), HostFunctions::default(), None)
2780 .unwrap();
2781 assert_eq!(sbox.call::<i32>("GetStatic", ()).unwrap(), 0);
2782 }
2783
2784 #[test]
2788 fn arc_clone_isolation_and_restore_compat() {
2789 let mut sbox = make_sandbox();
2790 sbox.call::<i32>("AddToStatic", 3i32).unwrap();
2791 let snapshot = sbox.snapshot().unwrap();
2792
2793 let mut a =
2794 MultiUseSandbox::from_snapshot(snapshot.clone(), HostFunctions::default(), None)
2795 .unwrap();
2796 let mut b =
2797 MultiUseSandbox::from_snapshot(snapshot.clone(), HostFunctions::default(), None)
2798 .unwrap();
2799 assert_eq!(a.call::<i32>("GetStatic", ()).unwrap(), 3);
2800 assert_eq!(b.call::<i32>("GetStatic", ()).unwrap(), 3);
2801
2802 a.call::<i32>("AddToStatic", 7i32).unwrap();
2803 assert_eq!(a.call::<i32>("GetStatic", ()).unwrap(), 10);
2804 assert_eq!(b.call::<i32>("GetStatic", ()).unwrap(), 3);
2805
2806 a.restore(snapshot.clone()).unwrap();
2807 b.restore(snapshot).unwrap();
2808 assert_eq!(a.call::<i32>("GetStatic", ()).unwrap(), 3);
2809 assert_eq!(b.call::<i32>("GetStatic", ()).unwrap(), 3);
2810 }
2811
2812 #[test]
2813 fn accepts_matching_host_functions() {
2814 let mut sbox = make_sandbox_with_add();
2815 sbox.call::<i32>("AddToStatic", 5i32).unwrap();
2816 let snap = sbox.snapshot().unwrap();
2817 let mut sbox2 =
2818 MultiUseSandbox::from_snapshot(snap, host_funcs_with_matching_add(), None).unwrap();
2819 assert_eq!(sbox2.call::<i32>("GetStatic", ()).unwrap(), 5);
2820 }
2821
2822 #[test]
2823 fn rejects_missing_host_function() {
2824 let mut sbox = make_sandbox_with_add();
2825 let snap = sbox.snapshot().unwrap();
2826 let err = MultiUseSandbox::from_snapshot(snap, HostFunctions::default(), None)
2827 .expect_err("missing `Add` must be rejected");
2828 assert!(
2829 matches!(
2830 &err,
2831 HyperlightError::SnapshotHostFunctionMismatch { missing, signature_mismatches }
2832 if missing.iter().any(|n| n == "Add") && signature_mismatches.is_empty()
2833 ),
2834 "got: {:?}",
2835 err
2836 );
2837 }
2838
2839 #[test]
2845 fn restore_rejects_missing_host_function() {
2846 let mut sbox_with_add = make_sandbox_with_add();
2847 let snap = sbox_with_add.snapshot().unwrap();
2848 let mut sbox_without_add = make_sandbox();
2849 let err = sbox_without_add
2850 .restore(snap)
2851 .expect_err("missing `Add` must be rejected on restore");
2852 assert!(
2853 matches!(
2854 &err,
2855 HyperlightError::SnapshotHostFunctionMismatch { missing, .. }
2856 if missing.iter().any(|n| n == "Add")
2857 ),
2858 "got: {:?}",
2859 err
2860 );
2861 }
2862
2863 #[test]
2866 fn restore_rejects_signature_mismatch() {
2867 let mut sbox_with_add = make_sandbox_with_add();
2868 let snap = sbox_with_add.snapshot().unwrap();
2869 let path = simple_guest_as_string().unwrap();
2870 let mut u = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2871 u.register_host_function("Add", |a: String, b: String| Ok(format!("{a}{b}")))
2872 .unwrap();
2873 let mut sbox_wrong_add = u.evolve().unwrap();
2874 let err = sbox_wrong_add
2875 .restore(snap)
2876 .expect_err("signature mismatch on `Add` must be rejected on restore");
2877 assert!(
2878 matches!(
2879 &err,
2880 HyperlightError::SnapshotHostFunctionMismatch { missing, signature_mismatches }
2881 if missing.is_empty() && signature_mismatches.iter().any(|s| s.contains("Add"))
2882 ),
2883 "got: {:?}",
2884 err
2885 );
2886 }
2887
2888 #[test]
2891 fn restore_across_sandboxes_with_superset_host_funcs() {
2892 let mut source = make_sandbox_with_add();
2893 source.call::<i32>("AddToStatic", 17i32).unwrap();
2894 let snap = source.snapshot().unwrap();
2895
2896 let path = simple_guest_as_string().unwrap();
2897 let mut u = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2898 u.register_host_function("Add", |a: i32, b: i32| Ok(a + b))
2899 .unwrap();
2900 u.register_host_function("Mul", |a: i32, b: i32| Ok(a * b))
2901 .unwrap();
2902 let mut target = u.evolve().unwrap();
2903
2904 target.restore(snap).unwrap();
2905 assert_eq!(target.call::<i32>("GetStatic", ()).unwrap(), 17);
2906 }
2907
2908 #[test]
2909 fn rejects_signature_mismatch() {
2910 let mut sbox = make_sandbox_with_add();
2911 let snap = sbox.snapshot().unwrap();
2912 let mut hf = HostFunctions::default();
2913 hf.register_host_function("Add", |a: String, b: String| Ok(format!("{a}{b}")))
2914 .unwrap();
2915 let err = MultiUseSandbox::from_snapshot(snap, hf, None)
2916 .expect_err("signature mismatch on `Add` must be rejected");
2917 assert!(
2918 matches!(
2919 &err,
2920 HyperlightError::SnapshotHostFunctionMismatch { missing, signature_mismatches }
2921 if missing.is_empty() && signature_mismatches.iter().any(|s| s.contains("Add"))
2922 ),
2923 "got: {:?}",
2924 err
2925 );
2926 }
2927
2928 #[test]
2931 fn accepts_extra_host_functions() {
2932 let mut sbox = make_sandbox_with_add();
2933 sbox.call::<i32>("AddToStatic", 9i32).unwrap();
2934 let snap = sbox.snapshot().unwrap();
2935 let mut hf = host_funcs_with_matching_add();
2936 hf.register_host_function("Mul", |a: i32, b: i32| Ok(a * b))
2937 .unwrap();
2938 let mut sbox2 = MultiUseSandbox::from_snapshot(snap, hf, None).unwrap();
2939 assert_eq!(sbox2.call::<i32>("GetStatic", ()).unwrap(), 9);
2940 }
2941
2942 #[test]
2945 fn re_snapshot_after_from_snapshot() {
2946 let mut sbox = make_sandbox();
2947 sbox.call::<i32>("AddToStatic", 4i32).unwrap();
2948 let snap1 = sbox.snapshot().unwrap();
2949
2950 let mut sbox2 =
2951 MultiUseSandbox::from_snapshot(snap1, HostFunctions::default(), None).unwrap();
2952 sbox2.call::<i32>("AddToStatic", 6i32).unwrap();
2953 let snap2 = sbox2.snapshot().unwrap();
2954
2955 sbox2.call::<i32>("AddToStatic", 100i32).unwrap();
2956 assert_eq!(sbox2.call::<i32>("GetStatic", ()).unwrap(), 110);
2957
2958 sbox2.restore(snap2.clone()).unwrap();
2959 assert_eq!(sbox2.call::<i32>("GetStatic", ()).unwrap(), 10);
2960
2961 let mut sbox3 =
2962 MultiUseSandbox::from_snapshot(snap2, HostFunctions::default(), None).unwrap();
2963 assert_eq!(sbox3.call::<i32>("GetStatic", ()).unwrap(), 10);
2964 }
2965
2966 #[test]
2969 fn supplied_host_function_is_callable() {
2970 let path = simple_guest_as_string().unwrap();
2971 let mut u = UninitializedSandbox::new(GuestBinary::FilePath(path), None).unwrap();
2972 u.register_host_function("Echo42", || Ok(1i64)).unwrap();
2973 let mut sbox = u.evolve().unwrap();
2974 let snap = sbox.snapshot().unwrap();
2975
2976 let mut hf = HostFunctions::default();
2977 hf.register_host_function("Echo42", || Ok(42i64)).unwrap();
2978 let mut sbox2 = MultiUseSandbox::from_snapshot(snap, hf, None).unwrap();
2979
2980 let got: i64 = sbox2
2981 .call(
2982 "CallGivenParamlessHostFuncThatReturnsI64",
2983 "Echo42".to_string(),
2984 )
2985 .unwrap();
2986 assert_eq!(got, 42);
2987 }
2988
2989 #[test]
2992 fn pre_init_snapshot_accepts_arbitrary_host_functions() {
2993 let path = simple_guest_as_string().unwrap();
2994 let snap =
2995 Snapshot::from_env(GuestBinary::FilePath(path), SandboxConfiguration::default())
2996 .unwrap();
2997 let mut hf = HostFunctions::default();
2998 hf.register_host_function("Unrelated", |a: i32| Ok(a + 1))
2999 .unwrap();
3000 let mut sbox = MultiUseSandbox::from_snapshot(Arc::new(snap), hf, None).unwrap();
3001 assert_eq!(sbox.call::<i32>("GetStatic", ()).unwrap(), 0);
3002 }
3003
3004 #[test]
3008 fn snapshot_generation_propagates() {
3009 let mut sbox = make_sandbox();
3010 sbox.call::<i32>("AddToStatic", 1i32).unwrap();
3011 let snap1 = sbox.snapshot().unwrap();
3012 let gen1 = snap1.snapshot_generation();
3013 sbox.call::<i32>("AddToStatic", 1i32).unwrap();
3014 let snap2 = sbox.snapshot().unwrap();
3015 let gen2 = snap2.snapshot_generation();
3016 assert_eq!(gen2, gen1 + 1);
3017
3018 let mut sbox2 =
3019 MultiUseSandbox::from_snapshot(snap2, HostFunctions::default(), None).unwrap();
3020 sbox2.call::<i32>("AddToStatic", 1i32).unwrap();
3021 let snap3 = sbox2.snapshot().unwrap();
3022 assert_eq!(snap3.snapshot_generation(), gen2 + 1);
3023 }
3024
3025 #[test]
3030 fn late_register_invalidates_snapshot_cache() {
3031 let mut sbox = make_sandbox();
3032 let _ = sbox.snapshot().unwrap();
3034
3035 sbox.register_host_function("Echo42", || Ok(42i64)).unwrap();
3036
3037 let snap = sbox.snapshot().unwrap();
3041 let err = MultiUseSandbox::from_snapshot(snap, HostFunctions::default(), None)
3042 .expect_err("late-registered `Echo42` must be required by the new snapshot");
3043 let msg = format!("{}", err);
3044 assert!(msg.contains("Echo42"), "got: {}", msg);
3045 }
3046 }
3047}