1use std::fmt::Debug;
18use std::option::Option;
19use std::path::Path;
20use std::sync::{Arc, Mutex};
21
22use tracing::{Span, instrument};
23use tracing_core::LevelFilter;
24
25use super::host_funcs::{FunctionRegistry, default_writer_func};
26use super::snapshot::Snapshot;
27use super::uninitialized_evolve::evolve_impl_multi_use;
28use crate::func::host_functions::{HostFunction, register_host_function};
29use crate::func::{ParameterTuple, SupportedReturnType};
30#[cfg(feature = "build-metadata")]
31use crate::log_build_details;
32use crate::mem::memory_region::{DEFAULT_GUEST_BLOB_MEM_FLAGS, MemoryRegionFlags};
33use crate::mem::mgr::SandboxMemoryManager;
34#[cfg(feature = "guest-counter")]
35use crate::mem::shared_mem::HostSharedMemory;
36use crate::mem::shared_mem::{ExclusiveSharedMemory, SharedMemory};
37use crate::sandbox::SandboxConfiguration;
38use crate::{MultiUseSandbox, Result, new_error};
39
40#[cfg(any(crashdump, gdb))]
41#[derive(Clone, Debug, Default)]
42pub(crate) struct SandboxRuntimeConfig {
43 #[cfg(crashdump)]
44 pub(crate) binary_path: Option<String>,
45 #[cfg(gdb)]
46 pub(crate) debug_info: Option<super::config::DebugInfo>,
47 #[cfg(crashdump)]
48 pub(crate) guest_core_dump: bool,
49 #[cfg(crashdump)]
56 pub(crate) entry_point: Option<u64>,
57}
58
59#[cfg(feature = "guest-counter")]
80pub struct GuestCounter {
81 inner: Mutex<GuestCounterInner>,
82}
83
84#[cfg(feature = "guest-counter")]
85struct GuestCounterInner {
86 deferred_hshm: Arc<Mutex<Option<HostSharedMemory>>>,
87 offset: usize,
88 value: u64,
89}
90
91#[cfg(feature = "guest-counter")]
92impl core::fmt::Debug for GuestCounter {
93 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
94 f.debug_struct("GuestCounter").finish_non_exhaustive()
95 }
96}
97
98#[cfg(feature = "guest-counter")]
99impl GuestCounter {
100 pub fn increment(&self) -> Result<()> {
102 let mut inner = self.inner.lock().map_err(|e| new_error!("{e}"))?;
103 let shm = {
104 let guard = inner.deferred_hshm.lock().map_err(|e| new_error!("{e}"))?;
105 guard
106 .as_ref()
107 .ok_or_else(|| {
108 new_error!("GuestCounter cannot be used before shared memory is built")
109 })?
110 .clone()
111 };
112 let new_value = inner
113 .value
114 .checked_add(1)
115 .ok_or_else(|| new_error!("GuestCounter overflow"))?;
116 shm.write::<u64>(inner.offset, new_value)?;
117 inner.value = new_value;
118 Ok(())
119 }
120
121 pub fn decrement(&self) -> Result<()> {
123 let mut inner = self.inner.lock().map_err(|e| new_error!("{e}"))?;
124 let shm = {
125 let guard = inner.deferred_hshm.lock().map_err(|e| new_error!("{e}"))?;
126 guard
127 .as_ref()
128 .ok_or_else(|| {
129 new_error!("GuestCounter cannot be used before shared memory is built")
130 })?
131 .clone()
132 };
133 let new_value = inner
134 .value
135 .checked_sub(1)
136 .ok_or_else(|| new_error!("GuestCounter underflow"))?;
137 shm.write::<u64>(inner.offset, new_value)?;
138 inner.value = new_value;
139 Ok(())
140 }
141
142 pub fn value(&self) -> Result<u64> {
144 let inner = self.inner.lock().map_err(|e| new_error!("{e}"))?;
145 Ok(inner.value)
146 }
147}
148
149pub struct UninitializedSandbox {
161 pub(crate) host_funcs: Arc<Mutex<FunctionRegistry>>,
163 pub(crate) mgr: SandboxMemoryManager<ExclusiveSharedMemory>,
165 pub(crate) max_guest_log_level: Option<LevelFilter>,
166 pub(crate) config: SandboxConfiguration,
167 #[cfg(any(crashdump, gdb))]
168 pub(crate) rt_cfg: SandboxRuntimeConfig,
169 pub(crate) load_info: crate::mem::exe::LoadInfo,
170 pub(crate) stack_top_gva: u64,
173 #[cfg(feature = "guest-counter")]
178 pub(crate) deferred_hshm: Arc<Mutex<Option<HostSharedMemory>>>,
179 #[cfg(feature = "guest-counter")]
183 counter_taken: std::sync::atomic::AtomicBool,
184 pub(crate) pending_file_mappings: Vec<super::file_mapping::PreparedFileMapping>,
187}
188
189impl Debug for UninitializedSandbox {
190 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191 f.debug_struct("UninitializedSandbox")
192 .field("memory_layout", &self.mgr.layout)
193 .finish()
194 }
195}
196
197#[derive(Debug)]
199pub enum GuestBinary<'a> {
200 Buffer(&'a [u8]),
202 FilePath(String),
204}
205impl<'a> GuestBinary<'a> {
206 pub fn canonicalize(&mut self) -> Result<()> {
214 if let GuestBinary::FilePath(p) = self {
215 let canon = Path::new(&p)
216 .canonicalize()
217 .map_err(|e| new_error!("GuestBinary not found: '{}': {}", p, e))?
218 .into_os_string()
219 .into_string()
220 .map_err(|e| new_error!("Error converting OsString to String: {:?}", e))?;
221 *self = GuestBinary::FilePath(canon)
222 }
223 Ok(())
224 }
225}
226
227#[derive(Debug)]
229pub struct GuestBlob<'a> {
230 pub data: &'a [u8],
232 pub permissions: MemoryRegionFlags,
235}
236
237impl<'a> From<&'a [u8]> for GuestBlob<'a> {
238 fn from(data: &'a [u8]) -> Self {
239 GuestBlob {
240 data,
241 permissions: DEFAULT_GUEST_BLOB_MEM_FLAGS,
242 }
243 }
244}
245
246#[derive(Debug)]
251pub struct GuestEnvironment<'a, 'b> {
252 pub guest_binary: GuestBinary<'a>,
254 pub init_data: Option<GuestBlob<'b>>,
256}
257
258impl<'a, 'b> GuestEnvironment<'a, 'b> {
259 pub fn new(guest_binary: GuestBinary<'a>, init_data: Option<&'b [u8]>) -> Self {
261 GuestEnvironment {
262 guest_binary,
263 init_data: init_data.map(GuestBlob::from),
264 }
265 }
266}
267
268impl<'a> From<GuestBinary<'a>> for GuestEnvironment<'a, '_> {
269 fn from(guest_binary: GuestBinary<'a>) -> Self {
270 GuestEnvironment {
271 guest_binary,
272 init_data: None,
273 }
274 }
275}
276
277impl UninitializedSandbox {
278 #[cfg(feature = "guest-counter")]
291 pub fn guest_counter(&mut self) -> Result<GuestCounter> {
292 use std::sync::atomic::Ordering;
293
294 use hyperlight_common::layout::SCRATCH_TOP_GUEST_COUNTER_OFFSET;
295
296 if self.counter_taken.swap(true, Ordering::Relaxed) {
297 return Err(new_error!(
298 "GuestCounter has already been created for this sandbox"
299 ));
300 }
301
302 let scratch_size = self.mgr.scratch_mem.mem_size();
303 if (SCRATCH_TOP_GUEST_COUNTER_OFFSET as usize) > scratch_size {
304 return Err(new_error!(
305 "scratch memory too small for guest counter (size {:#x}, need offset {:#x})",
306 scratch_size,
307 SCRATCH_TOP_GUEST_COUNTER_OFFSET,
308 ));
309 }
310
311 let offset = scratch_size - SCRATCH_TOP_GUEST_COUNTER_OFFSET as usize;
312 let deferred_hshm = self.deferred_hshm.clone();
313
314 Ok(GuestCounter {
315 inner: Mutex::new(GuestCounterInner {
316 deferred_hshm,
317 offset,
318 value: 0,
319 }),
320 })
321 }
322
323 fn from_snapshot(
330 snapshot: Arc<Snapshot>,
331 cfg: Option<SandboxConfiguration>,
332 #[cfg(crashdump)] binary_path: Option<String>,
333 ) -> Result<Self> {
334 #[cfg(feature = "build-metadata")]
335 log_build_details();
336
337 #[cfg(target_os = "windows")]
339 check_windows_version()?;
340
341 let sandbox_cfg = cfg.unwrap_or_default();
342
343 #[cfg(any(crashdump, gdb))]
344 let rt_cfg = {
345 #[cfg(crashdump)]
346 let guest_core_dump = sandbox_cfg.get_guest_core_dump();
347
348 #[cfg(gdb)]
349 let debug_info = sandbox_cfg.get_guest_debug_info();
350
351 SandboxRuntimeConfig {
352 #[cfg(crashdump)]
353 binary_path,
354 #[cfg(gdb)]
355 debug_info,
356 #[cfg(crashdump)]
357 guest_core_dump,
358 #[cfg(crashdump)]
361 entry_point: None,
362 }
363 };
364
365 let mem_mgr_wrapper =
366 SandboxMemoryManager::<ExclusiveSharedMemory>::from_snapshot(snapshot.as_ref())?;
367
368 let host_funcs = Arc::new(Mutex::new(FunctionRegistry::default()));
369
370 let mut sandbox = Self {
371 host_funcs,
372 mgr: mem_mgr_wrapper,
373 max_guest_log_level: None,
374 config: sandbox_cfg,
375 #[cfg(any(crashdump, gdb))]
376 rt_cfg,
377 load_info: snapshot.load_info(),
378 stack_top_gva: snapshot.stack_top_gva(),
379 #[cfg(feature = "guest-counter")]
380 deferred_hshm: Arc::new(Mutex::new(None)),
381 #[cfg(feature = "guest-counter")]
382 counter_taken: std::sync::atomic::AtomicBool::new(false),
383 pending_file_mappings: Vec::new(),
384 };
385
386 sandbox.register_print(default_writer_func)?;
388
389 crate::debug!("Sandbox created: {:#?}", sandbox);
390
391 Ok(sandbox)
392 }
393
394 #[instrument(
401 err(Debug),
402 skip(env),
403 parent = Span::current()
404 )]
405 pub fn new<'a, 'b>(
406 env: impl Into<GuestEnvironment<'a, 'b>>,
407 cfg: Option<SandboxConfiguration>,
408 ) -> Result<Self> {
409 let cfg = cfg.unwrap_or_default();
410 let env = env.into();
411 #[cfg(crashdump)]
412 let binary_path = match &env.guest_binary {
413 GuestBinary::FilePath(path) => Some(path.clone()),
414 GuestBinary::Buffer(_) => None,
415 };
416 let snapshot = Snapshot::from_env(env, cfg)?;
417 Self::from_snapshot(
418 Arc::new(snapshot),
419 Some(cfg),
420 #[cfg(crashdump)]
421 binary_path,
422 )
423 }
424
425 #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
431 pub fn evolve(self) -> Result<MultiUseSandbox> {
432 evolve_impl_multi_use(self)
433 }
434
435 #[instrument(err(Debug), skip(self, file_path, guest_base, label), parent = Span::current())]
449 pub fn map_file_cow(
450 &mut self,
451 file_path: &std::path::Path,
452 guest_base: u64,
453 label: Option<&str>,
454 ) -> crate::Result<u64> {
455 if self.pending_file_mappings.len() >= hyperlight_common::mem::MAX_FILE_MAPPINGS {
457 return Err(crate::HyperlightError::Error(format!(
458 "map_file_cow: file mapping limit reached ({} of {})",
459 self.pending_file_mappings.len(),
460 hyperlight_common::mem::MAX_FILE_MAPPINGS,
461 )));
462 }
463
464 let shared_size = self.mgr.shared_mem.mem_size() as u64;
467 let base_addr = crate::mem::layout::SandboxMemoryLayout::BASE_ADDRESS as u64;
468
469 let prepared = super::file_mapping::prepare_file_cow(file_path, guest_base, label)?;
470
471 let mapping_end = guest_base
473 .checked_add(prepared.size as u64)
474 .ok_or_else(|| {
475 crate::HyperlightError::Error(format!(
476 "map_file_cow: guest address overflow: {:#x} + {:#x}",
477 guest_base, prepared.size
478 ))
479 })?;
480 let shared_end = base_addr.checked_add(shared_size).ok_or_else(|| {
481 crate::HyperlightError::Error("shared memory end overflow".to_string())
482 })?;
483 if guest_base < shared_end && mapping_end > base_addr {
484 return Err(crate::HyperlightError::Error(format!(
485 "map_file_cow: mapping [{:#x}..{:#x}) overlaps sandbox shared memory [{:#x}..{:#x})",
486 guest_base, mapping_end, base_addr, shared_end,
487 )));
488 }
489
490 let size = prepared.size as u64;
491
492 let new_start = guest_base;
494 let new_end = mapping_end;
495 for existing in &self.pending_file_mappings {
496 let ex_start = existing.guest_base;
497 let ex_end = ex_start.checked_add(existing.size as u64).ok_or_else(|| {
498 crate::HyperlightError::Error(format!(
499 "map_file_cow: existing mapping address overflow: {:#x} + {:#x}",
500 ex_start, existing.size
501 ))
502 })?;
503 if new_start < ex_end && new_end > ex_start {
504 return Err(crate::HyperlightError::Error(format!(
505 "map_file_cow: mapping [{:#x}..{:#x}) overlaps existing mapping [{:#x}..{:#x})",
506 new_start, new_end, ex_start, ex_end,
507 )));
508 }
509 }
510
511 self.pending_file_mappings.push(prepared);
512 Ok(size)
513 }
514
515 pub fn shared_mem_size(&self) -> usize {
520 self.mgr.shared_mem.mem_size()
521 }
522
523 pub fn set_max_guest_log_level(&mut self, log_level: LevelFilter) {
528 self.max_guest_log_level = Some(log_level);
529 }
530
531 pub fn register<Args: ParameterTuple, Output: SupportedReturnType>(
533 &mut self,
534 name: impl AsRef<str>,
535 host_func: impl Into<HostFunction<Output, Args>>,
536 ) -> Result<()> {
537 register_host_function(host_func, self, name.as_ref())
538 }
539
540 pub fn register_print(
546 &mut self,
547 print_func: impl Into<HostFunction<i32, (String,)>>,
548 ) -> Result<()> {
549 self.register("HostPrint", print_func)
550 }
551
552 #[cfg(all(test, feature = "guest-counter"))]
556 fn simulate_build(&self) {
557 let hshm = self.mgr.scratch_mem.as_host_shared_memory();
558 #[allow(clippy::unwrap_used)]
559 {
560 *self.deferred_hshm.lock().unwrap() = Some(hshm);
561 }
562 }
563}
564#[cfg(target_os = "windows")]
567fn check_windows_version() -> Result<()> {
568 use windows_version::{OsVersion, is_server};
569 const WINDOWS_MAJOR: u32 = 10;
570 const WINDOWS_MINOR: u32 = 0;
571 const WINDOWS_PACK: u32 = 0;
572
573 if is_server() {
575 if OsVersion::current() < OsVersion::new(WINDOWS_MAJOR, WINDOWS_MINOR, WINDOWS_PACK, 20348)
576 {
577 return Err(new_error!(
578 "Hyperlight Requires Windows Server 2022 or newer"
579 ));
580 }
581 } else if OsVersion::current()
582 < OsVersion::new(WINDOWS_MAJOR, WINDOWS_MINOR, WINDOWS_PACK, 22000)
583 {
584 return Err(new_error!("Hyperlight Requires Windows 11 or newer"));
585 }
586 Ok(())
587}
588
589#[cfg(test)]
590mod tests {
591 use std::sync::Arc;
592 use std::sync::mpsc::channel;
593 use std::{fs, thread};
594
595 use crossbeam_queue::ArrayQueue;
596 use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnValue};
597 use hyperlight_testing::simple_guest_as_string;
598
599 use crate::sandbox::SandboxConfiguration;
600 use crate::sandbox::uninitialized::{GuestBinary, GuestEnvironment};
601 use crate::{MultiUseSandbox, Result, UninitializedSandbox, new_error};
602
603 #[test]
604 fn test_load_extra_blob() {
605 let binary_path = simple_guest_as_string().unwrap();
606 let buffer = [0xde, 0xad, 0xbe, 0xef];
607 let guest_env =
608 GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), Some(&buffer));
609
610 let uninitialized_sandbox = UninitializedSandbox::new(guest_env, None).unwrap();
611 let mut sandbox: MultiUseSandbox = uninitialized_sandbox.evolve().unwrap();
612
613 let res = sandbox
614 .call::<Vec<u8>>("ReadFromUserMemory", (4u64, buffer.to_vec()))
615 .expect("Failed to call ReadFromUserMemory");
616
617 assert_eq!(res, buffer.to_vec());
618 }
619
620 #[test]
621 fn test_new_sandbox() {
622 let binary_path = simple_guest_as_string().unwrap();
625 let sandbox = UninitializedSandbox::new(GuestBinary::FilePath(binary_path.clone()), None);
626 assert!(sandbox.is_ok());
627
628 let mut binary_path_does_not_exist = binary_path.clone();
631 binary_path_does_not_exist.push_str(".nonexistent");
632 let uninitialized_sandbox =
633 UninitializedSandbox::new(GuestBinary::FilePath(binary_path_does_not_exist), None);
634 assert!(uninitialized_sandbox.is_err());
635
636 let cfg = {
638 let mut cfg = SandboxConfiguration::default();
639 cfg.set_input_data_size(0x1000);
640 cfg.set_output_data_size(0x1000);
641 cfg.set_heap_size(0x1000);
642 Some(cfg)
643 };
644
645 let uninitialized_sandbox =
646 UninitializedSandbox::new(GuestBinary::FilePath(binary_path.clone()), cfg);
647 assert!(uninitialized_sandbox.is_ok());
648
649 let uninitialized_sandbox =
650 UninitializedSandbox::new(GuestBinary::FilePath(binary_path), None).unwrap();
651
652 let _sandbox: MultiUseSandbox = uninitialized_sandbox.evolve().unwrap();
655
656 let binary_path = simple_guest_as_string().unwrap();
659 let sandbox =
660 UninitializedSandbox::new(GuestBinary::Buffer(&fs::read(binary_path).unwrap()), None);
661 assert!(sandbox.is_ok());
662
663 let binary_path = simple_guest_as_string().unwrap();
666 let mut bytes = fs::read(binary_path).unwrap();
667 let _ = bytes.split_off(100);
668 let sandbox = UninitializedSandbox::new(GuestBinary::Buffer(&bytes), None);
669 assert!(sandbox.is_err());
670 }
671
672 #[test]
673 fn test_host_functions() {
674 let uninitialized_sandbox = || {
675 UninitializedSandbox::new(
676 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
677 None,
678 )
679 .unwrap()
680 };
681
682 {
684 let mut usbox = uninitialized_sandbox();
685
686 usbox.register("test0", |arg: i32| Ok(arg + 1)).unwrap();
687
688 let sandbox: Result<MultiUseSandbox> = usbox.evolve();
689 assert!(sandbox.is_ok());
690 let sandbox = sandbox.unwrap();
691
692 let host_funcs = sandbox
693 .host_funcs
694 .try_lock()
695 .map_err(|_| new_error!("Error locking"));
696
697 assert!(host_funcs.is_ok());
698
699 let res = host_funcs
700 .unwrap()
701 .call_host_function("test0", vec![ParameterValue::Int(1)])
702 .unwrap();
703
704 assert_eq!(res, ReturnValue::Int(2));
705 }
706
707 {
709 let mut usbox = uninitialized_sandbox();
710
711 usbox.register("test1", |a: i32, b: i32| Ok(a + b)).unwrap();
712
713 let sandbox: Result<MultiUseSandbox> = usbox.evolve();
714 assert!(sandbox.is_ok());
715 let sandbox = sandbox.unwrap();
716
717 let host_funcs = sandbox
718 .host_funcs
719 .try_lock()
720 .map_err(|_| new_error!("Error locking"));
721
722 assert!(host_funcs.is_ok());
723
724 let res = host_funcs
725 .unwrap()
726 .call_host_function(
727 "test1",
728 vec![ParameterValue::Int(1), ParameterValue::Int(2)],
729 )
730 .unwrap();
731
732 assert_eq!(res, ReturnValue::Int(3));
733 }
734
735 {
737 let mut usbox = uninitialized_sandbox();
738
739 usbox
740 .register("test2", |msg: String| {
741 println!("test2 called: {}", msg);
742 Ok(())
743 })
744 .unwrap();
745
746 let sandbox: Result<MultiUseSandbox> = usbox.evolve();
747 assert!(sandbox.is_ok());
748 let sandbox = sandbox.unwrap();
749
750 let host_funcs = sandbox
751 .host_funcs
752 .try_lock()
753 .map_err(|_| new_error!("Error locking"));
754
755 assert!(host_funcs.is_ok());
756
757 let res = host_funcs.unwrap().call_host_function("test2", vec![]);
758 assert!(res.is_err());
759 }
760
761 {
763 let usbox = uninitialized_sandbox();
764 let sandbox: Result<MultiUseSandbox> = usbox.evolve();
765 assert!(sandbox.is_ok());
766 let sandbox = sandbox.unwrap();
767
768 let host_funcs = sandbox
769 .host_funcs
770 .try_lock()
771 .map_err(|_| new_error!("Error locking"));
772
773 assert!(host_funcs.is_ok());
774
775 let res = host_funcs.unwrap().call_host_function("test4", vec![]);
776 assert!(res.is_err());
777 }
778 }
779
780 #[test]
781 fn test_host_print() {
782 let (tx, rx) = channel();
787
788 let writer = move |msg| {
789 let _ = tx.send(msg);
790 Ok(0)
791 };
792
793 let mut sandbox = UninitializedSandbox::new(
794 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
795 None,
796 )
797 .expect("Failed to create sandbox");
798
799 sandbox
800 .register_print(writer)
801 .expect("Failed to register host print function");
802
803 let host_funcs = sandbox
804 .host_funcs
805 .try_lock()
806 .map_err(|_| new_error!("Error locking"));
807
808 assert!(host_funcs.is_ok());
809
810 host_funcs.unwrap().host_print("test".to_string()).unwrap();
811
812 drop(sandbox);
813
814 let received_msgs: Vec<_> = rx.into_iter().collect();
815 assert_eq!(received_msgs, ["test"]);
816
817 fn fn_writer(msg: String) -> Result<i32> {
878 assert_eq!(msg, "test2");
879 Ok(0)
880 }
881
882 let mut sandbox = UninitializedSandbox::new(
883 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
884 None,
885 )
886 .expect("Failed to create sandbox");
887
888 sandbox
889 .register_print(fn_writer)
890 .expect("Failed to register host print function");
891
892 let host_funcs = sandbox
893 .host_funcs
894 .try_lock()
895 .map_err(|_| new_error!("Error locking"));
896
897 assert!(host_funcs.is_ok());
898
899 host_funcs.unwrap().host_print("test2".to_string()).unwrap();
900
901 let mut test_host_print = TestHostPrint::new();
904
905 let writer_closure = move |s| test_host_print.write(s);
908
909 let mut sandbox = UninitializedSandbox::new(
910 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
911 None,
912 )
913 .expect("Failed to create sandbox");
914
915 sandbox
916 .register_print(writer_closure)
917 .expect("Failed to register host print function");
918
919 let host_funcs = sandbox
920 .host_funcs
921 .try_lock()
922 .map_err(|_| new_error!("Error locking"));
923
924 assert!(host_funcs.is_ok());
925
926 host_funcs.unwrap().host_print("test3".to_string()).unwrap();
927 }
928
929 struct TestHostPrint {}
930
931 impl TestHostPrint {
932 fn new() -> Self {
933 TestHostPrint {}
934 }
935
936 fn write(&mut self, msg: String) -> Result<i32> {
937 assert_eq!(msg, "test3");
938 Ok(0)
939 }
940 }
941
942 #[test]
943 fn check_create_and_use_sandbox_on_different_threads() {
944 let unintializedsandbox_queue = Arc::new(ArrayQueue::<UninitializedSandbox>::new(10));
945 let sandbox_queue = Arc::new(ArrayQueue::<MultiUseSandbox>::new(10));
946
947 for i in 0..10 {
948 let simple_guest_path = simple_guest_as_string().expect("Guest Binary Missing");
949 let unintializedsandbox = {
950 let err_string = format!("failed to create UninitializedSandbox {i}");
951 let err_str = err_string.as_str();
952 UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_path), None)
953 .expect(err_str)
954 };
955
956 {
957 let err_string = format!("Failed to push UninitializedSandbox {i}");
958 let err_str = err_string.as_str();
959
960 unintializedsandbox_queue
961 .push(unintializedsandbox)
962 .expect(err_str);
963 }
964 }
965
966 let thread_handles = (0..10)
967 .map(|i| {
968 let uq = unintializedsandbox_queue.clone();
969 let sq = sandbox_queue.clone();
970 thread::spawn(move || {
971 let uninitialized_sandbox = uq.pop().unwrap_or_else(|| {
972 panic!("Failed to pop UninitializedSandbox thread {}", i)
973 });
974
975 let host_funcs = uninitialized_sandbox
976 .host_funcs
977 .try_lock()
978 .map_err(|_| new_error!("Error locking"));
979
980 assert!(host_funcs.is_ok());
981
982 host_funcs
983 .unwrap()
984 .host_print(format!("Print from UninitializedSandbox on Thread {}\n", i))
985 .unwrap();
986
987 let sandbox = uninitialized_sandbox.evolve().unwrap_or_else(|_| {
988 panic!("Failed to initialize UninitializedSandbox thread {}", i)
989 });
990
991 sq.push(sandbox).unwrap_or_else(|_| {
992 panic!("Failed to push UninitializedSandbox thread {}", i)
993 })
994 })
995 })
996 .collect::<Vec<_>>();
997
998 for handle in thread_handles {
999 handle.join().unwrap();
1000 }
1001
1002 let thread_handles = (0..10)
1003 .map(|i| {
1004 let sq = sandbox_queue.clone();
1005 thread::spawn(move || {
1006 let sandbox = sq
1007 .pop()
1008 .unwrap_or_else(|| panic!("Failed to pop Sandbox thread {}", i));
1009
1010 let host_funcs = sandbox
1011 .host_funcs
1012 .try_lock()
1013 .map_err(|_| new_error!("Error locking"));
1014
1015 assert!(host_funcs.is_ok());
1016
1017 host_funcs
1018 .unwrap()
1019 .host_print(format!("Print from Sandbox on Thread {}\n", i))
1020 .unwrap();
1021 })
1022 })
1023 .collect::<Vec<_>>();
1024
1025 for handle in thread_handles {
1026 handle.join().unwrap();
1027 }
1028 }
1029
1030 #[test]
1046 #[cfg(feature = "build-metadata")]
1047 fn test_trace_trace() {
1048 use hyperlight_testing::tracing_subscriber::TracingSubscriber;
1049 use tracing::Level;
1050 use tracing_core::Subscriber;
1051 use tracing_core::callsite::rebuild_interest_cache;
1052 use uuid::Uuid;
1053
1054 fn get_span_attr<'a>(span: &'a serde_json::Value, key: &str) -> Option<&'a str> {
1056 span.get("span")?.get("attributes")?.get(key)?.as_str()
1057 }
1058
1059 fn get_event_field<'a>(event: &'a serde_json::Value, field: &str) -> Option<&'a str> {
1061 event.get("event")?.get(field)?.as_str()
1062 }
1063
1064 fn get_event_metadata<'a>(event: &'a serde_json::Value, field: &str) -> Option<&'a str> {
1066 event.get("event")?.get("metadata")?.get(field)?.as_str()
1067 }
1068
1069 let subscriber = TracingSubscriber::new(Level::TRACE);
1070
1071 tracing::subscriber::with_default(subscriber.clone(), || {
1072 let bad_path = simple_guest_as_string().unwrap() + "does_not_exist";
1077 let _ = UninitializedSandbox::new(GuestBinary::FilePath(bad_path.clone()), None);
1078
1079 rebuild_interest_cache();
1084
1085 subscriber.clear();
1087
1088 let correlation_id = Uuid::new_v4().to_string();
1089 let _span = tracing::error_span!("test_trace_logs", %correlation_id).entered();
1090
1091 let (test_span_id, span_meta) = subscriber
1093 .current_span()
1094 .into_inner()
1095 .expect("Should be inside a span");
1096 assert_eq!(span_meta.name(), "test_trace_logs");
1097
1098 let span_data = subscriber.get_span(test_span_id.into_u64());
1100 let recorded_id =
1101 get_span_attr(&span_data, "correlation_id").expect("correlation_id not found");
1102 assert_eq!(recorded_id, correlation_id);
1103
1104 let result = UninitializedSandbox::new(GuestBinary::FilePath(bad_path), None);
1107 assert!(result.is_err(), "Sandbox creation should fail");
1108
1109 let (current_id, _) = subscriber
1111 .current_span()
1112 .into_inner()
1113 .expect("Should still be inside a span");
1114 assert_eq!(
1115 current_id.into_u64(),
1116 test_span_id.into_u64(),
1117 "Should still be in the test span"
1118 );
1119
1120 let all_spans = subscriber.get_all_spans();
1123 let _new_span_entry = all_spans
1124 .iter()
1125 .find(|&(&id, _)| {
1126 id != test_span_id.into_u64()
1127 && subscriber.get_span_metadata(id).name() == "new"
1128 })
1129 .expect("Expected a span named 'new' from UninitializedSandbox::new");
1130
1131 let events = subscriber.get_events();
1133 assert_eq!(events.len(), 1, "Expected exactly one error event");
1134
1135 let event = &events[0];
1136 let level = get_event_metadata(event, "level").expect("event should have level");
1137 let error = get_event_field(event, "error").expect("event should have error field");
1138 let target = get_event_metadata(event, "target").expect("event should have target");
1139 let module_path =
1140 get_event_metadata(event, "module_path").expect("event should have module_path");
1141
1142 assert_eq!(level, "ERROR");
1143 assert!(
1144 error.contains("GuestBinary not found"),
1145 "Error should mention 'GuestBinary not found', got: {error}"
1146 );
1147 assert_eq!(target, "hyperlight_host::sandbox::uninitialized");
1148 assert_eq!(module_path, "hyperlight_host::sandbox::uninitialized");
1149 });
1150 }
1151
1152 #[test]
1153 #[ignore]
1154 #[cfg(feature = "build-metadata")]
1157 fn test_log_trace() {
1158 use std::path::PathBuf;
1159
1160 use hyperlight_testing::logger::{LOGGER as TEST_LOGGER, Logger as TestLogger};
1161 use log::Level;
1162 use tracing_core::callsite::rebuild_interest_cache;
1163
1164 {
1165 TestLogger::initialize_test_logger();
1166 TEST_LOGGER.set_max_level(log::LevelFilter::Trace);
1167
1168 rebuild_interest_cache();
1172
1173 let mut invalid_binary_path = simple_guest_as_string().unwrap();
1174 invalid_binary_path.push_str("does_not_exist");
1175
1176 let sbox = UninitializedSandbox::new(GuestBinary::FilePath(invalid_binary_path), None);
1177 assert!(sbox.is_err());
1178
1179 let num_calls = TEST_LOGGER.num_log_calls();
1193 assert_eq!(13, num_calls);
1194
1195 let logcall = TEST_LOGGER.get_log_call(0).unwrap();
1198 assert_eq!(Level::Info, logcall.level);
1199
1200 assert!(logcall.args.starts_with("new; cfg"));
1201 assert_eq!("hyperlight_host::sandbox::uninitialized", logcall.target);
1202
1203 let logcall = TEST_LOGGER.get_log_call(1).unwrap();
1206 assert_eq!(Level::Trace, logcall.level);
1207 assert_eq!(logcall.args, "-> new;");
1208 assert_eq!("tracing::span::active", logcall.target);
1209
1210 let logcall = TEST_LOGGER.get_log_call(10).unwrap();
1213 assert_eq!(Level::Error, logcall.level);
1214 assert!(
1215 logcall
1216 .args
1217 .starts_with("error=Error(\"GuestBinary not found:")
1218 );
1219 assert_eq!("hyperlight_host::sandbox::uninitialized", logcall.target);
1220
1221 let logcall = TEST_LOGGER.get_log_call(11).unwrap();
1224 assert_eq!(Level::Trace, logcall.level);
1225 assert_eq!(logcall.args, "<- new;");
1226 assert_eq!("tracing::span::active", logcall.target);
1227
1228 let logcall = TEST_LOGGER.get_log_call(12).unwrap();
1231 assert_eq!(Level::Trace, logcall.level);
1232 assert_eq!(logcall.args, "-- new;");
1233 assert_eq!("tracing::span", logcall.target);
1234 }
1235 {
1236 TEST_LOGGER.clear_log_calls();
1238 TEST_LOGGER.set_max_level(log::LevelFilter::Info);
1239
1240 let mut valid_binary_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1241 valid_binary_path.push("src");
1242 valid_binary_path.push("sandbox");
1243 valid_binary_path.push("initialized.rs");
1244
1245 let sbox = UninitializedSandbox::new(
1246 GuestBinary::FilePath(valid_binary_path.into_os_string().into_string().unwrap()),
1247 None,
1248 );
1249 assert!(sbox.is_err());
1250
1251 let num_calls = TEST_LOGGER.num_log_calls();
1254 assert_eq!(2, num_calls);
1255
1256 let logcall = TEST_LOGGER.get_log_call(0).unwrap();
1259 assert_eq!(Level::Info, logcall.level);
1260
1261 assert!(logcall.args.starts_with("new; cfg"));
1262 assert_eq!("hyperlight_host::sandbox::uninitialized", logcall.target);
1263
1264 let logcall = TEST_LOGGER.get_log_call(1).unwrap();
1267 assert_eq!(Level::Error, logcall.level);
1268 assert!(
1269 logcall
1270 .args
1271 .starts_with("error=Error(\"GuestBinary not found:")
1272 );
1273 assert_eq!("hyperlight_host::sandbox::uninitialized", logcall.target);
1274 }
1275 {
1276 TEST_LOGGER.clear_log_calls();
1277 TEST_LOGGER.set_max_level(log::LevelFilter::Error);
1278
1279 let sbox = {
1280 let res = UninitializedSandbox::new(
1281 GuestBinary::FilePath(simple_guest_as_string().unwrap()),
1282 None,
1283 );
1284 res.unwrap()
1285 };
1286 let _: Result<MultiUseSandbox> = sbox.evolve();
1287
1288 let num_calls = TEST_LOGGER.num_log_calls();
1289
1290 assert_eq!(0, num_calls);
1291 }
1292 }
1293
1294 #[test]
1295 fn test_invalid_path() {
1296 let invalid_path = "some/path/that/does/not/exist";
1297 let sbox = UninitializedSandbox::new(GuestBinary::FilePath(invalid_path.to_string()), None);
1298 println!("{:?}", sbox);
1299 #[cfg(target_os = "windows")]
1300 assert!(
1301 matches!(sbox, Err(e) if e.to_string().contains("GuestBinary not found: 'some/path/that/does/not/exist': The system cannot find the path specified. (os error 3)"))
1302 );
1303 #[cfg(target_os = "linux")]
1304 assert!(
1305 matches!(sbox, Err(e) if e.to_string().contains("GuestBinary not found: 'some/path/that/does/not/exist': No such file or directory (os error 2)"))
1306 );
1307 }
1308
1309 #[test]
1310 fn test_from_snapshot_various_configurations() {
1311 use crate::sandbox::snapshot::Snapshot;
1312
1313 let binary_path = simple_guest_as_string().unwrap();
1314
1315 {
1317 let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None);
1318
1319 let snapshot = Arc::new(
1320 Snapshot::from_env(env, Default::default())
1321 .expect("Failed to create snapshot with default config"),
1322 );
1323
1324 let sandbox1 = UninitializedSandbox::from_snapshot(
1326 snapshot.clone(),
1327 None,
1328 #[cfg(crashdump)]
1329 Some(binary_path.clone()),
1330 )
1331 .expect("Failed to create first sandbox from snapshot");
1332
1333 let sandbox2 = UninitializedSandbox::from_snapshot(
1335 snapshot.clone(),
1336 None,
1337 #[cfg(crashdump)]
1338 Some(binary_path.clone()),
1339 )
1340 .expect("Failed to create second sandbox from snapshot");
1341
1342 let _evolved1: MultiUseSandbox = sandbox1.evolve().expect("Failed to evolve sandbox1");
1344 let _evolved2: MultiUseSandbox = sandbox2.evolve().expect("Failed to evolve sandbox2");
1345 }
1346
1347 {
1349 let mut cfg = SandboxConfiguration::default();
1350 cfg.set_heap_size(16 * 1024 * 1024); let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None);
1353
1354 let snapshot = Arc::new(
1355 Snapshot::from_env(env, cfg)
1356 .expect("Failed to create snapshot with custom heap size"),
1357 );
1358
1359 let sandbox = UninitializedSandbox::from_snapshot(
1360 snapshot,
1361 None,
1362 #[cfg(crashdump)]
1363 Some(binary_path.clone()),
1364 )
1365 .expect("Failed to create sandbox from snapshot with custom heap");
1366
1367 let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox");
1368 }
1369
1370 {
1372 let mut cfg = SandboxConfiguration::default();
1373 cfg.set_scratch_size(256 * 1024); let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None);
1376
1377 let snapshot = Arc::new(
1378 Snapshot::from_env(env, cfg)
1379 .expect("Failed to create snapshot with custom stack size"),
1380 );
1381
1382 let sandbox = UninitializedSandbox::from_snapshot(
1383 snapshot,
1384 None,
1385 #[cfg(crashdump)]
1386 Some(binary_path.clone()),
1387 )
1388 .expect("Failed to create sandbox from snapshot with custom stack");
1389
1390 let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox");
1391 }
1392
1393 {
1395 let mut cfg = SandboxConfiguration::default();
1396 cfg.set_input_data_size(64 * 1024); cfg.set_output_data_size(64 * 1024); let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None);
1400
1401 let snapshot = Arc::new(
1402 Snapshot::from_env(env, cfg)
1403 .expect("Failed to create snapshot with custom buffer sizes"),
1404 );
1405
1406 let sandbox = UninitializedSandbox::from_snapshot(
1407 snapshot,
1408 None,
1409 #[cfg(crashdump)]
1410 Some(binary_path.clone()),
1411 )
1412 .expect("Failed to create sandbox from snapshot with custom buffers");
1413
1414 let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox");
1415 }
1416
1417 {
1419 let mut cfg = SandboxConfiguration::default();
1420 cfg.set_heap_size(32 * 1024 * 1024); cfg.set_scratch_size(256 * 1024 * 2); cfg.set_input_data_size(128 * 1024); cfg.set_output_data_size(128 * 1024); let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None);
1426
1427 let snapshot = Arc::new(
1428 Snapshot::from_env(env, cfg)
1429 .expect("Failed to create snapshot with all custom settings"),
1430 );
1431
1432 let sandbox1 = UninitializedSandbox::from_snapshot(
1434 snapshot.clone(),
1435 None,
1436 #[cfg(crashdump)]
1437 Some(binary_path.clone()),
1438 )
1439 .expect("Failed to create sandbox1 from fully customized snapshot");
1440 let sandbox2 = UninitializedSandbox::from_snapshot(
1441 snapshot.clone(),
1442 None,
1443 #[cfg(crashdump)]
1444 Some(binary_path.clone()),
1445 )
1446 .expect("Failed to create sandbox2 from fully customized snapshot");
1447 let sandbox3 = UninitializedSandbox::from_snapshot(
1448 snapshot.clone(),
1449 None,
1450 #[cfg(crashdump)]
1451 Some(binary_path.clone()),
1452 )
1453 .expect("Failed to create sandbox3 from fully customized snapshot");
1454
1455 let _evolved1: MultiUseSandbox = sandbox1.evolve().expect("Failed to evolve sandbox1");
1456 let _evolved2: MultiUseSandbox = sandbox2.evolve().expect("Failed to evolve sandbox2");
1457 let _evolved3: MultiUseSandbox = sandbox3.evolve().expect("Failed to evolve sandbox3");
1458 }
1459
1460 {
1462 let binary_bytes = fs::read(&binary_path).expect("Failed to read binary file");
1463
1464 let snapshot = Arc::new(
1465 Snapshot::from_env(GuestBinary::Buffer(&binary_bytes), Default::default())
1466 .expect("Failed to create snapshot from buffer"),
1467 );
1468
1469 let sandbox = UninitializedSandbox::from_snapshot(
1470 snapshot,
1471 None,
1472 #[cfg(crashdump)]
1473 None,
1474 )
1475 .expect("Failed to create sandbox from buffer-based snapshot");
1476
1477 let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox");
1478 }
1479
1480 {
1482 let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None);
1483
1484 let snapshot = Arc::new(
1485 Snapshot::from_env(env, Default::default()).expect("Failed to create snapshot"),
1486 );
1487
1488 let mut sandbox = UninitializedSandbox::from_snapshot(
1489 snapshot,
1490 None,
1491 #[cfg(crashdump)]
1492 Some(binary_path.clone()),
1493 )
1494 .expect("Failed to create sandbox from snapshot");
1495
1496 sandbox
1498 .register("CustomAdd", |a: i32, b: i32| Ok(a + b))
1499 .expect("Failed to register custom function");
1500
1501 let evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox");
1502
1503 let host_funcs = evolved
1505 .host_funcs
1506 .try_lock()
1507 .expect("Failed to lock host funcs");
1508
1509 let result = host_funcs
1510 .call_host_function(
1511 "CustomAdd",
1512 vec![ParameterValue::Int(10), ParameterValue::Int(20)],
1513 )
1514 .expect("Failed to call CustomAdd");
1515
1516 assert_eq!(result, ReturnValue::Int(30));
1517 }
1518
1519 {
1521 let init_data = [0xCA, 0xFE, 0xBA, 0xBE];
1522 let guest_env =
1523 GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), Some(&init_data));
1524
1525 let snapshot = Arc::new(
1526 Snapshot::from_env(guest_env, Default::default())
1527 .expect("Failed to create snapshot with init data"),
1528 );
1529
1530 let sandbox = UninitializedSandbox::from_snapshot(
1531 snapshot,
1532 None,
1533 #[cfg(crashdump)]
1534 Some(binary_path.clone()),
1535 )
1536 .expect("Failed to create sandbox from snapshot with init data");
1537
1538 let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox");
1539 }
1540
1541 {
1543 let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None);
1544 let orig_snapshot = Arc::new(
1545 Snapshot::from_env(env, Default::default())
1546 .expect("Failed to create snapshot with default config"),
1547 );
1548 let orig_sandbox = UninitializedSandbox::from_snapshot(
1549 orig_snapshot,
1550 None,
1551 #[cfg(crashdump)]
1552 Some(binary_path.clone()),
1553 )
1554 .expect("Failed to create orig_sandbox");
1555 let mut initialized_sandbox = orig_sandbox
1556 .evolve()
1557 .expect("Failed to evolve orig_sandbox");
1558 let new_snapshot = initialized_sandbox
1559 .snapshot()
1560 .expect("Failed to create new_snapshot");
1561 let new_sandbox = UninitializedSandbox::from_snapshot(
1562 new_snapshot,
1563 None,
1564 #[cfg(crashdump)]
1565 Some(binary_path.clone()),
1566 )
1567 .expect("Failed to create new_sandbox");
1568 let _evolved = new_sandbox.evolve().expect("Failed to evolve new_sandbox");
1569 }
1570 }
1571
1572 #[cfg(feature = "guest-counter")]
1573 mod guest_counter_tests {
1574 use hyperlight_testing::simple_guest_as_string;
1575
1576 use crate::UninitializedSandbox;
1577 use crate::sandbox::uninitialized::GuestBinary;
1578
1579 fn make_sandbox() -> UninitializedSandbox {
1580 UninitializedSandbox::new(
1581 GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
1582 None,
1583 )
1584 .expect("Failed to create sandbox")
1585 }
1586
1587 #[test]
1588 fn create_guest_counter() {
1589 let mut sandbox = make_sandbox();
1590 let counter = sandbox.guest_counter();
1591 assert!(counter.is_ok());
1592 }
1593
1594 #[test]
1595 fn only_one_counter_allowed() {
1596 let mut sandbox = make_sandbox();
1597 let _c1 = sandbox.guest_counter().unwrap();
1598 let c2 = sandbox.guest_counter();
1599 assert!(c2.is_err());
1600 }
1601
1602 #[test]
1603 fn fails_before_build() {
1604 let mut sandbox = make_sandbox();
1605 let counter = sandbox.guest_counter().unwrap();
1606 assert!(counter.increment().is_err());
1607 assert!(counter.decrement().is_err());
1608 }
1609
1610 #[test]
1611 fn increment_decrement() {
1612 let mut sandbox = make_sandbox();
1613 let counter = sandbox.guest_counter().unwrap();
1614 sandbox.simulate_build();
1615
1616 counter.increment().unwrap();
1617 assert_eq!(counter.value().unwrap(), 1);
1618 counter.increment().unwrap();
1619 assert_eq!(counter.value().unwrap(), 2);
1620 counter.decrement().unwrap();
1621 assert_eq!(counter.value().unwrap(), 1);
1622 }
1623
1624 #[test]
1625 fn underflow_returns_error() {
1626 let mut sandbox = make_sandbox();
1627 let counter = sandbox.guest_counter().unwrap();
1628 sandbox.simulate_build();
1629
1630 assert_eq!(counter.value().unwrap(), 0);
1631 let result = counter.decrement();
1632 assert!(result.is_err());
1633 }
1634 }
1635}