1use std::fmt::Debug;
5use std::option::Option;
6use std::path::PathBuf;
7use std::sync::{Arc, Mutex};
8
9use tracing::{Span, instrument};
10use tracing_core::LevelFilter;
11
12use super::host_funcs::FunctionRegistry;
13use super::snapshot::Snapshot;
14use super::uninitialized_evolve::evolve_impl_multi_use;
15use crate::func::host_functions::{HostFunction, register_host_function};
16use crate::func::{ParameterTuple, SupportedReturnType};
17#[cfg(feature = "build-metadata")]
18use crate::log_build_details;
19use crate::mem::memory_region::{DEFAULT_GUEST_BLOB_MEM_FLAGS, MemoryRegionFlags};
20use crate::mem::mgr::SandboxMemoryManager;
21use crate::mem::shared_mem::{ExclusiveSharedMemory, SharedMemory};
22use crate::sandbox::SandboxConfiguration;
23use crate::{MultiUseSandbox, Result, new_error};
24
25#[cfg(any(crashdump, gdb))]
26#[derive(Clone, Debug, Default)]
27pub(crate) struct SandboxRuntimeConfig {
28 #[cfg(crashdump)]
29 pub(crate) binary_path: Option<PathBuf>,
30 #[cfg(gdb)]
31 pub(crate) debug_info: Option<super::config::DebugInfo>,
32 #[cfg(crashdump)]
33 pub(crate) guest_core_dump: bool,
34 #[cfg(crashdump)]
41 pub(crate) entry_point: Option<u64>,
42}
43
44pub struct UninitializedSandbox {
56 pub(crate) host_funcs: Arc<Mutex<FunctionRegistry>>,
58 pub(crate) mgr: SandboxMemoryManager<ExclusiveSharedMemory>,
60 pub(crate) max_guest_log_level: Option<LevelFilter>,
61 pub(crate) config: SandboxConfiguration,
62 #[cfg(any(crashdump, gdb))]
63 pub(crate) rt_cfg: SandboxRuntimeConfig,
64 pub(crate) load_info: crate::mem::exe::LoadInfo,
65 pub(crate) stack_top_gva: u64,
68 pub(crate) pending_file_mappings: Vec<super::file_mapping::PreparedFileMapping>,
71}
72
73impl Debug for UninitializedSandbox {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 f.debug_struct("UninitializedSandbox")
76 .field("memory_layout", &self.mgr.layout)
77 .finish()
78 }
79}
80
81#[derive(Debug)]
83pub enum GuestBinary {
84 Buffer(Vec<u8>),
86 FilePath(PathBuf),
88}
89impl GuestBinary {
90 pub fn canonicalize(&mut self) -> Result<()> {
98 if let GuestBinary::FilePath(p) = self {
99 *p = p
100 .canonicalize()
101 .map_err(|e| new_error!("GuestBinary not found: '{}': {}", p.display(), e))?;
102 }
103 Ok(())
104 }
105}
106
107#[derive(Debug)]
109pub struct GuestBlob<'a> {
110 pub data: &'a [u8],
112 pub permissions: MemoryRegionFlags,
115}
116
117impl<'a> From<&'a [u8]> for GuestBlob<'a> {
118 fn from(data: &'a [u8]) -> Self {
119 GuestBlob {
120 data,
121 permissions: DEFAULT_GUEST_BLOB_MEM_FLAGS,
122 }
123 }
124}
125
126#[derive(Debug)]
133pub struct GuestEnvironment<'b> {
134 pub guest_binary: GuestBinary,
136 pub init_data: Option<GuestBlob<'b>>,
138}
139
140impl<'b> GuestEnvironment<'b> {
141 pub fn new(guest_binary: GuestBinary, init_data: Option<&'b [u8]>) -> Self {
143 GuestEnvironment {
144 guest_binary,
145 init_data: init_data.map(GuestBlob::from),
146 }
147 }
148}
149
150impl From<GuestBinary> for GuestEnvironment<'_> {
151 fn from(guest_binary: GuestBinary) -> Self {
152 GuestEnvironment {
153 guest_binary,
154 init_data: None,
155 }
156 }
157}
158
159impl UninitializedSandbox {
160 fn from_snapshot(
167 snapshot: Arc<Snapshot>,
168 cfg: Option<SandboxConfiguration>,
169 #[cfg(crashdump)] binary_path: Option<PathBuf>,
170 ) -> Result<Self> {
171 #[cfg(feature = "build-metadata")]
172 log_build_details();
173
174 #[cfg(target_os = "windows")]
176 check_windows_version()?;
177
178 let sandbox_cfg = cfg.unwrap_or_default();
179
180 #[cfg(any(crashdump, gdb))]
181 let rt_cfg = {
182 #[cfg(crashdump)]
183 let guest_core_dump = sandbox_cfg.get_guest_core_dump();
184
185 #[cfg(gdb)]
186 let debug_info = sandbox_cfg.get_guest_debug_info();
187
188 SandboxRuntimeConfig {
189 #[cfg(crashdump)]
190 binary_path,
191 #[cfg(gdb)]
192 debug_info,
193 #[cfg(crashdump)]
194 guest_core_dump,
195 #[cfg(crashdump)]
198 entry_point: None,
199 }
200 };
201
202 let mem_mgr_wrapper =
203 SandboxMemoryManager::<ExclusiveSharedMemory>::from_snapshot(snapshot.as_ref())?;
204
205 let host_funcs = Arc::new(Mutex::new(FunctionRegistry::with_default_host_print()));
206
207 let sandbox = Self {
208 host_funcs,
209 mgr: mem_mgr_wrapper,
210 max_guest_log_level: None,
211 config: sandbox_cfg,
212 #[cfg(any(crashdump, gdb))]
213 rt_cfg,
214 load_info: snapshot.load_info(),
215 stack_top_gva: snapshot.stack_top_gva(),
216 pending_file_mappings: Vec::new(),
217 };
218
219 crate::debug!("Sandbox created: {:#?}", sandbox);
220
221 Ok(sandbox)
222 }
223
224 #[instrument(
231 err(Debug),
232 skip(env),
233 parent = Span::current()
234 )]
235 pub fn new<'b>(
236 env: impl Into<GuestEnvironment<'b>>,
237 cfg: Option<SandboxConfiguration>,
238 ) -> Result<Self> {
239 let cfg = cfg.unwrap_or_default();
240 let env = env.into();
241 #[cfg(crashdump)]
242 let binary_path = match &env.guest_binary {
243 GuestBinary::FilePath(path) => Some(path.clone()),
244 GuestBinary::Buffer(_) => None,
245 };
246 let snapshot = Snapshot::from_env(env, cfg)?;
247 Self::from_snapshot(
248 Arc::new(snapshot),
249 Some(cfg),
250 #[cfg(crashdump)]
251 binary_path,
252 )
253 }
254
255 #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
261 pub fn evolve(self) -> Result<MultiUseSandbox> {
262 evolve_impl_multi_use(self)
263 }
264
265 #[instrument(err(Debug), skip(self, file_path, guest_base), parent = Span::current())]
276 pub fn map_file_cow(
277 &mut self,
278 file_path: &std::path::Path,
279 guest_base: u64,
280 ) -> crate::Result<u64> {
281 let shared_size = self.mgr.shared_mem.mem_size() as u64;
284 let base_addr = crate::mem::layout::SandboxMemoryLayout::BASE_ADDRESS as u64;
285
286 let prepared = super::file_mapping::prepare_file_cow(file_path, guest_base)?;
287
288 let mapping_end = guest_base
290 .checked_add(prepared.size as u64)
291 .ok_or_else(|| {
292 crate::HyperlightError::Error(format!(
293 "map_file_cow: guest address overflow: {:#x} + {:#x}",
294 guest_base, prepared.size
295 ))
296 })?;
297 let shared_end = base_addr.checked_add(shared_size).ok_or_else(|| {
298 crate::HyperlightError::Error("shared memory end overflow".to_string())
299 })?;
300 if guest_base < shared_end && mapping_end > base_addr {
301 return Err(crate::HyperlightError::Error(format!(
302 "map_file_cow: mapping [{:#x}..{:#x}) overlaps sandbox shared memory [{:#x}..{:#x})",
303 guest_base, mapping_end, base_addr, shared_end,
304 )));
305 }
306
307 let size = prepared.size as u64;
308
309 let new_start = guest_base;
311 let new_end = mapping_end;
312 for existing in &self.pending_file_mappings {
313 let ex_start = existing.guest_base;
314 let ex_end = ex_start.checked_add(existing.size as u64).ok_or_else(|| {
315 crate::HyperlightError::Error(format!(
316 "map_file_cow: existing mapping address overflow: {:#x} + {:#x}",
317 ex_start, existing.size
318 ))
319 })?;
320 if new_start < ex_end && new_end > ex_start {
321 return Err(crate::HyperlightError::Error(format!(
322 "map_file_cow: mapping [{:#x}..{:#x}) overlaps existing mapping [{:#x}..{:#x})",
323 new_start, new_end, ex_start, ex_end,
324 )));
325 }
326 }
327
328 self.pending_file_mappings.push(prepared);
329 Ok(size)
330 }
331
332 pub fn shared_mem_size(&self) -> usize {
337 self.mgr.shared_mem.mem_size()
338 }
339
340 pub fn set_max_guest_log_level(&mut self, log_level: LevelFilter) {
345 self.max_guest_log_level = Some(log_level);
346 }
347
348 pub fn register<Args: ParameterTuple, Output: SupportedReturnType>(
350 &mut self,
351 name: impl AsRef<str>,
352 host_func: impl Into<HostFunction<Output, Args>>,
353 ) -> Result<()> {
354 register_host_function(host_func, self, name.as_ref())
355 }
356
357 pub fn register_print(
363 &mut self,
364 print_func: impl Into<HostFunction<i32, (String,)>>,
365 ) -> Result<()> {
366 self.register("HostPrint", print_func)
367 }
368}
369#[cfg(target_os = "windows")]
372fn check_windows_version() -> Result<()> {
373 use windows_version::{OsVersion, is_server};
374 const WINDOWS_MAJOR: u32 = 10;
375 const WINDOWS_MINOR: u32 = 0;
376 const WINDOWS_PACK: u32 = 0;
377
378 if is_server() {
380 if OsVersion::current() < OsVersion::new(WINDOWS_MAJOR, WINDOWS_MINOR, WINDOWS_PACK, 20348)
381 {
382 return Err(new_error!(
383 "Hyperlight Requires Windows Server 2022 or newer"
384 ));
385 }
386 } else if OsVersion::current()
387 < OsVersion::new(WINDOWS_MAJOR, WINDOWS_MINOR, WINDOWS_PACK, 22000)
388 {
389 return Err(new_error!("Hyperlight Requires Windows 11 or newer"));
390 }
391 Ok(())
392}
393
394#[cfg(test)]
395mod tests {
396 use std::sync::Arc;
397 use std::sync::mpsc::channel;
398 use std::{fs, thread};
399
400 use crossbeam_queue::ArrayQueue;
401 use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnValue};
402 use hyperlight_testing::simple_guest_as_pathbuf;
403
404 use crate::sandbox::SandboxConfiguration;
405 use crate::sandbox::uninitialized::{GuestBinary, GuestEnvironment};
406 use crate::{MultiUseSandbox, Result, UninitializedSandbox, new_error};
407
408 #[cfg(target_os = "linux")]
409 #[test]
410 fn guest_binary_loads_from_non_utf8_path() {
411 use std::ffi::OsString;
412 use std::os::unix::ffi::OsStringExt;
413
414 let temp_dir = tempfile::tempdir().unwrap();
415 let guest_path = temp_dir
416 .path()
417 .join(OsString::from_vec(b"guest-\xff".to_vec()));
418 fs::copy(simple_guest_as_pathbuf(), &guest_path).unwrap();
419
420 let sandbox = UninitializedSandbox::new(GuestBinary::FilePath(guest_path), None);
421
422 assert!(sandbox.is_ok());
423 }
424
425 #[test]
426 fn test_load_extra_blob() {
427 let binary_path = simple_guest_as_pathbuf();
428 let buffer = [0xde, 0xad, 0xbe, 0xef];
429 let guest_env =
430 GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), Some(&buffer));
431
432 let uninitialized_sandbox = UninitializedSandbox::new(guest_env, None).unwrap();
433 let mut sandbox = uninitialized_sandbox.evolve().unwrap();
434
435 let res = sandbox
436 .call::<Vec<u8>>("ReadFromUserMemory", (4u64, buffer.to_vec()))
437 .expect("Failed to call ReadFromUserMemory");
438
439 assert_eq!(res, buffer.to_vec());
440 }
441
442 #[test]
443 fn test_new_sandbox() {
444 let binary_path = simple_guest_as_pathbuf();
447 let sandbox = UninitializedSandbox::new(GuestBinary::FilePath(binary_path.clone()), None);
448 assert!(sandbox.is_ok());
449
450 let mut binary_path_does_not_exist = binary_path.clone();
453 binary_path_does_not_exist
454 .as_mut_os_string()
455 .push(".nonexistent");
456 let uninitialized_sandbox =
457 UninitializedSandbox::new(GuestBinary::FilePath(binary_path_does_not_exist), None);
458 assert!(uninitialized_sandbox.is_err());
459
460 let cfg = {
462 let mut cfg = SandboxConfiguration::default();
463 cfg.set_input_data_size(0x1000);
464 cfg.set_output_data_size(0x1000);
465 cfg.set_heap_size(0x1000);
466 Some(cfg)
467 };
468
469 let uninitialized_sandbox =
470 UninitializedSandbox::new(GuestBinary::FilePath(binary_path.clone()), cfg);
471 assert!(uninitialized_sandbox.is_ok());
472
473 let uninitialized_sandbox =
474 UninitializedSandbox::new(GuestBinary::FilePath(binary_path), None).unwrap();
475
476 let _sandbox = uninitialized_sandbox.evolve().unwrap();
479
480 let binary_path = simple_guest_as_pathbuf();
483 let sandbox =
484 UninitializedSandbox::new(GuestBinary::Buffer(fs::read(binary_path).unwrap()), None);
485 assert!(sandbox.is_ok());
486
487 let binary_path = simple_guest_as_pathbuf();
490 let mut bytes = fs::read(binary_path).unwrap();
491 let _ = bytes.split_off(100);
492 let sandbox = UninitializedSandbox::new(GuestBinary::Buffer(bytes), None);
493 assert!(sandbox.is_err());
494 }
495
496 #[test]
497 fn test_host_functions() {
498 let uninitialized_sandbox = || {
499 UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None)
500 .unwrap()
501 };
502
503 {
505 let mut usbox = uninitialized_sandbox();
506
507 usbox.register("test0", |arg: i32| Ok(arg + 1)).unwrap();
508
509 let sandbox: Result<MultiUseSandbox> = usbox.evolve();
510 assert!(sandbox.is_ok());
511 let sandbox = sandbox.unwrap();
512
513 let host_funcs = sandbox
514 .host_funcs
515 .try_lock()
516 .map_err(|_| new_error!("Error locking"));
517
518 assert!(host_funcs.is_ok());
519
520 let res = host_funcs
521 .unwrap()
522 .call_host_function("test0", vec![ParameterValue::Int(1)])
523 .unwrap();
524
525 assert_eq!(res, ReturnValue::Int(2));
526 }
527
528 {
530 let mut usbox = uninitialized_sandbox();
531
532 usbox.register("test1", |a: i32, b: i32| Ok(a + b)).unwrap();
533
534 let sandbox: Result<MultiUseSandbox> = usbox.evolve();
535 assert!(sandbox.is_ok());
536 let sandbox = sandbox.unwrap();
537
538 let host_funcs = sandbox
539 .host_funcs
540 .try_lock()
541 .map_err(|_| new_error!("Error locking"));
542
543 assert!(host_funcs.is_ok());
544
545 let res = host_funcs
546 .unwrap()
547 .call_host_function(
548 "test1",
549 vec![ParameterValue::Int(1), ParameterValue::Int(2)],
550 )
551 .unwrap();
552
553 assert_eq!(res, ReturnValue::Int(3));
554 }
555
556 {
558 let mut usbox = uninitialized_sandbox();
559
560 usbox
561 .register("test2", |msg: String| {
562 println!("test2 called: {}", msg);
563 Ok(())
564 })
565 .unwrap();
566
567 let sandbox: Result<MultiUseSandbox> = usbox.evolve();
568 assert!(sandbox.is_ok());
569 let sandbox = sandbox.unwrap();
570
571 let host_funcs = sandbox
572 .host_funcs
573 .try_lock()
574 .map_err(|_| new_error!("Error locking"));
575
576 assert!(host_funcs.is_ok());
577
578 let res = host_funcs.unwrap().call_host_function("test2", vec![]);
579 assert!(res.is_err());
580 }
581
582 {
584 let usbox = uninitialized_sandbox();
585 let sandbox: Result<MultiUseSandbox> = usbox.evolve();
586 assert!(sandbox.is_ok());
587 let sandbox = sandbox.unwrap();
588
589 let host_funcs = sandbox
590 .host_funcs
591 .try_lock()
592 .map_err(|_| new_error!("Error locking"));
593
594 assert!(host_funcs.is_ok());
595
596 let res = host_funcs.unwrap().call_host_function("test4", vec![]);
597 assert!(res.is_err());
598 }
599 }
600
601 #[test]
602 fn test_host_print() {
603 let (tx, rx) = channel();
608
609 let writer = move |msg| {
610 let _ = tx.send(msg);
611 Ok(0)
612 };
613
614 let mut sandbox =
615 UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None)
616 .expect("Failed to create sandbox");
617
618 sandbox
619 .register_print(writer)
620 .expect("Failed to register host print function");
621
622 let host_funcs = sandbox
623 .host_funcs
624 .try_lock()
625 .map_err(|_| new_error!("Error locking"));
626
627 assert!(host_funcs.is_ok());
628
629 host_funcs.unwrap().host_print("test".to_string()).unwrap();
630
631 drop(sandbox);
632
633 let received_msgs: Vec<_> = rx.into_iter().collect();
634 assert_eq!(received_msgs, ["test"]);
635
636 fn fn_writer(msg: String) -> Result<i32> {
697 assert_eq!(msg, "test2");
698 Ok(0)
699 }
700
701 let mut sandbox =
702 UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None)
703 .expect("Failed to create sandbox");
704
705 sandbox
706 .register_print(fn_writer)
707 .expect("Failed to register host print function");
708
709 let host_funcs = sandbox
710 .host_funcs
711 .try_lock()
712 .map_err(|_| new_error!("Error locking"));
713
714 assert!(host_funcs.is_ok());
715
716 host_funcs.unwrap().host_print("test2".to_string()).unwrap();
717
718 let mut test_host_print = TestHostPrint::new();
721
722 let writer_closure = move |s| test_host_print.write(s);
725
726 let mut sandbox =
727 UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None)
728 .expect("Failed to create sandbox");
729
730 sandbox
731 .register_print(writer_closure)
732 .expect("Failed to register host print function");
733
734 let host_funcs = sandbox
735 .host_funcs
736 .try_lock()
737 .map_err(|_| new_error!("Error locking"));
738
739 assert!(host_funcs.is_ok());
740
741 host_funcs.unwrap().host_print("test3".to_string()).unwrap();
742 }
743
744 struct TestHostPrint {}
745
746 impl TestHostPrint {
747 fn new() -> Self {
748 TestHostPrint {}
749 }
750
751 fn write(&mut self, msg: String) -> Result<i32> {
752 assert_eq!(msg, "test3");
753 Ok(0)
754 }
755 }
756
757 #[test]
758 fn check_create_and_use_sandbox_on_different_threads() {
759 let unintializedsandbox_queue = Arc::new(ArrayQueue::<UninitializedSandbox>::new(10));
760 let sandbox_queue = Arc::new(ArrayQueue::<MultiUseSandbox>::new(10));
761
762 for i in 0..10 {
763 let simple_guest_path = simple_guest_as_pathbuf();
764 let unintializedsandbox = {
765 let err_string = format!("failed to create UninitializedSandbox {i}");
766 let err_str = err_string.as_str();
767 UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_path), None)
768 .expect(err_str)
769 };
770
771 {
772 let err_string = format!("Failed to push UninitializedSandbox {i}");
773 let err_str = err_string.as_str();
774
775 unintializedsandbox_queue
776 .push(unintializedsandbox)
777 .expect(err_str);
778 }
779 }
780
781 let thread_handles = (0..10)
782 .map(|i| {
783 let uq = unintializedsandbox_queue.clone();
784 let sq = sandbox_queue.clone();
785 thread::spawn(move || {
786 let uninitialized_sandbox = uq.pop().unwrap_or_else(|| {
787 panic!("Failed to pop UninitializedSandbox thread {}", i)
788 });
789
790 let host_funcs = uninitialized_sandbox
791 .host_funcs
792 .try_lock()
793 .map_err(|_| new_error!("Error locking"));
794
795 assert!(host_funcs.is_ok());
796
797 host_funcs
798 .unwrap()
799 .host_print(format!("Print from UninitializedSandbox on Thread {}\n", i))
800 .unwrap();
801
802 let sandbox = uninitialized_sandbox.evolve().unwrap_or_else(|_| {
803 panic!("Failed to initialize UninitializedSandbox thread {}", i)
804 });
805
806 sq.push(sandbox).unwrap_or_else(|_| {
807 panic!("Failed to push UninitializedSandbox thread {}", i)
808 })
809 })
810 })
811 .collect::<Vec<_>>();
812
813 for handle in thread_handles {
814 handle.join().unwrap();
815 }
816
817 let thread_handles = (0..10)
818 .map(|i| {
819 let sq = sandbox_queue.clone();
820 thread::spawn(move || {
821 let sandbox = sq
822 .pop()
823 .unwrap_or_else(|| panic!("Failed to pop Sandbox thread {}", i));
824
825 let host_funcs = sandbox
826 .host_funcs
827 .try_lock()
828 .map_err(|_| new_error!("Error locking"));
829
830 assert!(host_funcs.is_ok());
831
832 host_funcs
833 .unwrap()
834 .host_print(format!("Print from Sandbox on Thread {}\n", i))
835 .unwrap();
836 })
837 })
838 .collect::<Vec<_>>();
839
840 for handle in thread_handles {
841 handle.join().unwrap();
842 }
843 }
844
845 #[test]
861 #[cfg(feature = "build-metadata")]
862 fn test_trace_trace() {
863 use hyperlight_testing::tracing_subscriber::TracingSubscriber;
864 use tracing::Level;
865 use tracing_core::Subscriber;
866 use tracing_core::callsite::rebuild_interest_cache;
867 use uuid::Uuid;
868
869 fn get_span_attr<'a>(span: &'a serde_json::Value, key: &str) -> Option<&'a str> {
871 span.get("span")?.get("attributes")?.get(key)?.as_str()
872 }
873
874 fn get_event_field<'a>(event: &'a serde_json::Value, field: &str) -> Option<&'a str> {
876 event.get("event")?.get(field)?.as_str()
877 }
878
879 fn get_event_metadata<'a>(event: &'a serde_json::Value, field: &str) -> Option<&'a str> {
881 event.get("event")?.get("metadata")?.get(field)?.as_str()
882 }
883
884 let subscriber = TracingSubscriber::new(Level::TRACE);
885
886 tracing::subscriber::with_default(subscriber.clone(), || {
887 let mut bad_path = simple_guest_as_pathbuf();
892 bad_path.as_mut_os_string().push("does_not_exist");
893 let _ = UninitializedSandbox::new(GuestBinary::FilePath(bad_path.clone()), None);
894
895 rebuild_interest_cache();
900
901 subscriber.clear();
903
904 let correlation_id = Uuid::new_v4().to_string();
905 let _span = tracing::error_span!("test_trace_logs", %correlation_id).entered();
906
907 let (test_span_id, span_meta) = subscriber
909 .current_span()
910 .into_inner()
911 .expect("Should be inside a span");
912 assert_eq!(span_meta.name(), "test_trace_logs");
913
914 let span_data = subscriber.get_span(test_span_id.into_u64());
916 let recorded_id =
917 get_span_attr(&span_data, "correlation_id").expect("correlation_id not found");
918 assert_eq!(recorded_id, correlation_id);
919
920 let result = UninitializedSandbox::new(GuestBinary::FilePath(bad_path), None);
923 assert!(result.is_err(), "Sandbox creation should fail");
924
925 let (current_id, _) = subscriber
927 .current_span()
928 .into_inner()
929 .expect("Should still be inside a span");
930 assert_eq!(
931 current_id.into_u64(),
932 test_span_id.into_u64(),
933 "Should still be in the test span"
934 );
935
936 let all_spans = subscriber.get_all_spans();
939 let _new_span_entry = all_spans
940 .iter()
941 .find(|&(&id, _)| {
942 id != test_span_id.into_u64()
943 && subscriber.get_span_metadata(id).name() == "new"
944 })
945 .expect("Expected a span named 'new' from UninitializedSandbox::new");
946
947 let events = subscriber.get_events();
949 assert_eq!(events.len(), 1, "Expected exactly one error event");
950
951 let event = &events[0];
952 let level = get_event_metadata(event, "level").expect("event should have level");
953 let error = get_event_field(event, "error").expect("event should have error field");
954 let target = get_event_metadata(event, "target").expect("event should have target");
955 let module_path =
956 get_event_metadata(event, "module_path").expect("event should have module_path");
957
958 assert_eq!(level, "ERROR");
959 assert!(
960 error.contains("GuestBinary not found"),
961 "Error should mention 'GuestBinary not found', got: {error}"
962 );
963 assert_eq!(target, "hyperlight_host::sandbox::uninitialized");
964 assert_eq!(module_path, "hyperlight_host::sandbox::uninitialized");
965 });
966 }
967
968 #[test]
969 #[ignore]
970 #[cfg(feature = "build-metadata")]
973 fn test_log_trace() {
974 use std::path::PathBuf;
975
976 use hyperlight_testing::logger::{LOGGER as TEST_LOGGER, Logger as TestLogger};
977 use log::Level;
978 use tracing_core::callsite::rebuild_interest_cache;
979
980 {
981 TestLogger::initialize_test_logger();
982 TEST_LOGGER.set_max_level(log::LevelFilter::Trace);
983
984 rebuild_interest_cache();
988
989 let mut invalid_binary_path = simple_guest_as_pathbuf();
990 invalid_binary_path
991 .as_mut_os_string()
992 .push("does_not_exist");
993
994 let sbox = UninitializedSandbox::new(GuestBinary::FilePath(invalid_binary_path), None);
995 assert!(sbox.is_err());
996
997 let num_calls = TEST_LOGGER.num_log_calls();
1011 assert_eq!(13, num_calls);
1012
1013 let logcall = TEST_LOGGER.get_log_call(0).unwrap();
1016 assert_eq!(Level::Info, logcall.level);
1017
1018 assert!(logcall.args.starts_with("new; cfg"));
1019 assert_eq!("hyperlight_host::sandbox::uninitialized", logcall.target);
1020
1021 let logcall = TEST_LOGGER.get_log_call(1).unwrap();
1024 assert_eq!(Level::Trace, logcall.level);
1025 assert_eq!(logcall.args, "-> new;");
1026 assert_eq!("tracing::span::active", logcall.target);
1027
1028 let logcall = TEST_LOGGER.get_log_call(10).unwrap();
1031 assert_eq!(Level::Error, logcall.level);
1032 assert!(
1033 logcall
1034 .args
1035 .starts_with("error=Error(\"GuestBinary not found:")
1036 );
1037 assert_eq!("hyperlight_host::sandbox::uninitialized", logcall.target);
1038
1039 let logcall = TEST_LOGGER.get_log_call(11).unwrap();
1042 assert_eq!(Level::Trace, logcall.level);
1043 assert_eq!(logcall.args, "<- new;");
1044 assert_eq!("tracing::span::active", logcall.target);
1045
1046 let logcall = TEST_LOGGER.get_log_call(12).unwrap();
1049 assert_eq!(Level::Trace, logcall.level);
1050 assert_eq!(logcall.args, "-- new;");
1051 assert_eq!("tracing::span", logcall.target);
1052 }
1053 {
1054 TEST_LOGGER.clear_log_calls();
1056 TEST_LOGGER.set_max_level(log::LevelFilter::Info);
1057
1058 let mut valid_binary_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1059 valid_binary_path.push("src");
1060 valid_binary_path.push("sandbox");
1061 valid_binary_path.push("initialized.rs");
1062
1063 let sbox = UninitializedSandbox::new(GuestBinary::FilePath(valid_binary_path), None);
1064 assert!(sbox.is_err());
1065
1066 let num_calls = TEST_LOGGER.num_log_calls();
1069 assert_eq!(2, num_calls);
1070
1071 let logcall = TEST_LOGGER.get_log_call(0).unwrap();
1074 assert_eq!(Level::Info, logcall.level);
1075
1076 assert!(logcall.args.starts_with("new; cfg"));
1077 assert_eq!("hyperlight_host::sandbox::uninitialized", logcall.target);
1078
1079 let logcall = TEST_LOGGER.get_log_call(1).unwrap();
1082 assert_eq!(Level::Error, logcall.level);
1083 assert!(
1084 logcall
1085 .args
1086 .starts_with("error=Error(\"GuestBinary not found:")
1087 );
1088 assert_eq!("hyperlight_host::sandbox::uninitialized", logcall.target);
1089 }
1090 {
1091 TEST_LOGGER.clear_log_calls();
1092 TEST_LOGGER.set_max_level(log::LevelFilter::Error);
1093
1094 let sbox = {
1095 let res = UninitializedSandbox::new(
1096 GuestBinary::FilePath(simple_guest_as_pathbuf()),
1097 None,
1098 );
1099 res.unwrap()
1100 };
1101 let _: Result<MultiUseSandbox> = sbox.evolve();
1102
1103 let num_calls = TEST_LOGGER.num_log_calls();
1104
1105 assert_eq!(0, num_calls);
1106 }
1107 }
1108
1109 #[test]
1110 fn test_invalid_path() {
1111 let invalid_path = "some/path/that/does/not/exist";
1112 let sbox = UninitializedSandbox::new(GuestBinary::FilePath(invalid_path.into()), None);
1113 println!("{:?}", sbox);
1114 #[cfg(target_os = "windows")]
1115 assert!(
1116 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)"))
1117 );
1118 #[cfg(target_os = "linux")]
1119 assert!(
1120 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)"))
1121 );
1122 }
1123
1124 #[test]
1125 fn test_from_snapshot_various_configurations() {
1126 use crate::sandbox::snapshot::Snapshot;
1127
1128 let binary_path = simple_guest_as_pathbuf();
1129
1130 {
1132 let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None);
1133
1134 let snapshot = Arc::new(
1135 Snapshot::from_env(env, Default::default())
1136 .expect("Failed to create snapshot with default config"),
1137 );
1138
1139 let sandbox1 = UninitializedSandbox::from_snapshot(
1141 snapshot.clone(),
1142 None,
1143 #[cfg(crashdump)]
1144 Some(binary_path.clone()),
1145 )
1146 .expect("Failed to create first sandbox from snapshot");
1147
1148 let sandbox2 = UninitializedSandbox::from_snapshot(
1150 snapshot.clone(),
1151 None,
1152 #[cfg(crashdump)]
1153 Some(binary_path.clone()),
1154 )
1155 .expect("Failed to create second sandbox from snapshot");
1156
1157 let _evolved1 = sandbox1.evolve().expect("Failed to evolve sandbox1");
1159 let _evolved2 = sandbox2.evolve().expect("Failed to evolve sandbox2");
1160 }
1161
1162 {
1164 let mut cfg = SandboxConfiguration::default();
1165 cfg.set_heap_size(16 * 1024 * 1024); let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None);
1168
1169 let snapshot = Arc::new(
1170 Snapshot::from_env(env, cfg)
1171 .expect("Failed to create snapshot with custom heap size"),
1172 );
1173
1174 let sandbox = UninitializedSandbox::from_snapshot(
1175 snapshot,
1176 None,
1177 #[cfg(crashdump)]
1178 Some(binary_path.clone()),
1179 )
1180 .expect("Failed to create sandbox from snapshot with custom heap");
1181
1182 let _evolved = sandbox.evolve().expect("Failed to evolve sandbox");
1183 }
1184
1185 {
1187 let mut cfg = SandboxConfiguration::default();
1188 cfg.set_scratch_size(256 * 1024); let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None);
1191
1192 let snapshot = Arc::new(
1193 Snapshot::from_env(env, cfg)
1194 .expect("Failed to create snapshot with custom stack size"),
1195 );
1196
1197 let sandbox = UninitializedSandbox::from_snapshot(
1198 snapshot,
1199 None,
1200 #[cfg(crashdump)]
1201 Some(binary_path.clone()),
1202 )
1203 .expect("Failed to create sandbox from snapshot with custom stack");
1204
1205 let _evolved = sandbox.evolve().expect("Failed to evolve sandbox");
1206 }
1207
1208 {
1210 let mut cfg = SandboxConfiguration::default();
1211 cfg.set_input_data_size(64 * 1024); cfg.set_output_data_size(64 * 1024); let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None);
1215
1216 let snapshot = Arc::new(
1217 Snapshot::from_env(env, cfg)
1218 .expect("Failed to create snapshot with custom buffer sizes"),
1219 );
1220
1221 let sandbox = UninitializedSandbox::from_snapshot(
1222 snapshot,
1223 None,
1224 #[cfg(crashdump)]
1225 Some(binary_path.clone()),
1226 )
1227 .expect("Failed to create sandbox from snapshot with custom buffers");
1228
1229 let _evolved = sandbox.evolve().expect("Failed to evolve sandbox");
1230 }
1231
1232 {
1234 let mut cfg = SandboxConfiguration::default();
1235 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);
1241
1242 let snapshot = Arc::new(
1243 Snapshot::from_env(env, cfg)
1244 .expect("Failed to create snapshot with all custom settings"),
1245 );
1246
1247 let sandbox1 = UninitializedSandbox::from_snapshot(
1249 snapshot.clone(),
1250 None,
1251 #[cfg(crashdump)]
1252 Some(binary_path.clone()),
1253 )
1254 .expect("Failed to create sandbox1 from fully customized snapshot");
1255 let sandbox2 = UninitializedSandbox::from_snapshot(
1256 snapshot.clone(),
1257 None,
1258 #[cfg(crashdump)]
1259 Some(binary_path.clone()),
1260 )
1261 .expect("Failed to create sandbox2 from fully customized snapshot");
1262 let sandbox3 = UninitializedSandbox::from_snapshot(
1263 snapshot.clone(),
1264 None,
1265 #[cfg(crashdump)]
1266 Some(binary_path.clone()),
1267 )
1268 .expect("Failed to create sandbox3 from fully customized snapshot");
1269
1270 let _evolved1 = sandbox1.evolve().expect("Failed to evolve sandbox1");
1271 let _evolved2 = sandbox2.evolve().expect("Failed to evolve sandbox2");
1272 let _evolved3 = sandbox3.evolve().expect("Failed to evolve sandbox3");
1273 }
1274
1275 {
1277 let binary_bytes = fs::read(&binary_path).expect("Failed to read binary file");
1278
1279 let snapshot = Arc::new(
1280 Snapshot::from_env(GuestBinary::Buffer(binary_bytes), Default::default())
1281 .expect("Failed to create snapshot from buffer"),
1282 );
1283
1284 let sandbox = UninitializedSandbox::from_snapshot(
1285 snapshot,
1286 None,
1287 #[cfg(crashdump)]
1288 None,
1289 )
1290 .expect("Failed to create sandbox from buffer-based snapshot");
1291
1292 let _evolved = sandbox.evolve().expect("Failed to evolve sandbox");
1293 }
1294
1295 {
1297 let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None);
1298
1299 let snapshot = Arc::new(
1300 Snapshot::from_env(env, Default::default()).expect("Failed to create snapshot"),
1301 );
1302
1303 let mut sandbox = UninitializedSandbox::from_snapshot(
1304 snapshot,
1305 None,
1306 #[cfg(crashdump)]
1307 Some(binary_path.clone()),
1308 )
1309 .expect("Failed to create sandbox from snapshot");
1310
1311 sandbox
1313 .register("CustomAdd", |a: i32, b: i32| Ok(a + b))
1314 .expect("Failed to register custom function");
1315
1316 let evolved = sandbox.evolve().expect("Failed to evolve sandbox");
1317
1318 let host_funcs = evolved
1320 .host_funcs
1321 .try_lock()
1322 .expect("Failed to lock host funcs");
1323
1324 let result = host_funcs
1325 .call_host_function(
1326 "CustomAdd",
1327 vec![ParameterValue::Int(10), ParameterValue::Int(20)],
1328 )
1329 .expect("Failed to call CustomAdd");
1330
1331 assert_eq!(result, ReturnValue::Int(30));
1332 }
1333
1334 {
1336 let init_data = [0xCA, 0xFE, 0xBA, 0xBE];
1337 let guest_env =
1338 GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), Some(&init_data));
1339
1340 let snapshot = Arc::new(
1341 Snapshot::from_env(guest_env, Default::default())
1342 .expect("Failed to create snapshot with init data"),
1343 );
1344
1345 let sandbox = UninitializedSandbox::from_snapshot(
1346 snapshot,
1347 None,
1348 #[cfg(crashdump)]
1349 Some(binary_path.clone()),
1350 )
1351 .expect("Failed to create sandbox from snapshot with init data");
1352
1353 let _evolved = sandbox.evolve().expect("Failed to evolve sandbox");
1354 }
1355
1356 {
1358 let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None);
1359 let orig_snapshot = Arc::new(
1360 Snapshot::from_env(env, Default::default())
1361 .expect("Failed to create snapshot with default config"),
1362 );
1363 let orig_sandbox = UninitializedSandbox::from_snapshot(
1364 orig_snapshot,
1365 None,
1366 #[cfg(crashdump)]
1367 Some(binary_path.clone()),
1368 )
1369 .expect("Failed to create orig_sandbox");
1370 let mut initialized_sandbox = orig_sandbox
1371 .evolve()
1372 .expect("Failed to evolve orig_sandbox");
1373 let new_snapshot = initialized_sandbox
1374 .snapshot()
1375 .expect("Failed to create new_snapshot");
1376 let new_sandbox = UninitializedSandbox::from_snapshot(
1377 new_snapshot,
1378 None,
1379 #[cfg(crashdump)]
1380 Some(binary_path.clone()),
1381 )
1382 .expect("Failed to create new_sandbox");
1383 let _evolved = new_sandbox.evolve().expect("Failed to evolve new_sandbox");
1384 }
1385 }
1386}