hyperlight_host/sandbox/
uninitialized.rs

1/*
2Copyright 2025  The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17use std::fmt::Debug;
18use std::option::Option;
19use std::path::Path;
20use std::sync::{Arc, Mutex};
21
22use log::LevelFilter;
23use tracing::{Span, instrument};
24
25use super::host_funcs::{FunctionRegistry, default_writer_func};
26use super::uninitialized_evolve::evolve_impl_multi_use;
27use crate::func::host_functions::{HostFunction, register_host_function};
28use crate::func::{ParameterTuple, SupportedReturnType};
29#[cfg(feature = "build-metadata")]
30use crate::log_build_details;
31use crate::mem::exe::ExeInfo;
32use crate::mem::memory_region::{DEFAULT_GUEST_BLOB_MEM_FLAGS, MemoryRegionFlags};
33use crate::mem::mgr::SandboxMemoryManager;
34use crate::mem::shared_mem::ExclusiveSharedMemory;
35use crate::sandbox::SandboxConfiguration;
36use crate::{MultiUseSandbox, Result, new_error};
37
38#[cfg(seccomp)]
39const EXTRA_ALLOWED_SYSCALLS_FOR_WRITER_FUNC: &[super::ExtraAllowedSyscall] = &[
40    // Fuzzing fails without `mmap` being an allowed syscall on our seccomp filter.
41    // All fuzzing does is call `PrintOutput` (which calls `HostPrint` ). Thing is, `println!`
42    // is designed to be thread-safe in Rust and the std lib ensures this by using
43    // buffered I/O, which I think relies on `mmap`. This gets surfaced in fuzzing with an
44    // OOM error, which I think is happening because `println!` is not being able to allocate
45    // more memory for its buffers for the fuzzer's huge inputs.
46    libc::SYS_mmap,
47    libc::SYS_brk,
48    libc::SYS_mprotect,
49    #[cfg(mshv)]
50    libc::SYS_close,
51];
52
53#[cfg(any(crashdump, gdb))]
54#[derive(Clone, Debug, Default)]
55pub(crate) struct SandboxRuntimeConfig {
56    #[cfg(crashdump)]
57    pub(crate) binary_path: Option<String>,
58    #[cfg(gdb)]
59    pub(crate) debug_info: Option<super::config::DebugInfo>,
60    #[cfg(crashdump)]
61    pub(crate) guest_core_dump: bool,
62}
63
64/// A preliminary sandbox that represents allocated memory and registered host functions,
65/// but has not yet created the underlying virtual machine.
66///
67/// This struct holds the configuration and setup needed for a sandbox without actually
68/// creating the VM. It allows you to:
69/// - Set up memory layout and load guest binary data
70/// - Register host functions that will be available to the guest
71/// - Configure sandbox settings before VM creation
72///
73/// The virtual machine is not created until you call [`evolve`](Self::evolve) to transform
74/// this into an initialized [`MultiUseSandbox`].
75pub struct UninitializedSandbox {
76    /// Registered host functions
77    pub(crate) host_funcs: Arc<Mutex<FunctionRegistry>>,
78    /// The memory manager for the sandbox.
79    pub(crate) mgr: SandboxMemoryManager<ExclusiveSharedMemory>,
80    pub(crate) max_guest_log_level: Option<LevelFilter>,
81    pub(crate) config: SandboxConfiguration,
82    #[cfg(any(crashdump, gdb))]
83    pub(crate) rt_cfg: SandboxRuntimeConfig,
84    pub(crate) load_info: crate::mem::exe::LoadInfo,
85}
86
87impl Debug for UninitializedSandbox {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        f.debug_struct("UninitializedSandbox")
90            .field("memory_layout", &self.mgr.layout)
91            .finish()
92    }
93}
94
95/// A `GuestBinary` is either a buffer or the file path to some data (e.g., a guest binary).
96#[derive(Debug)]
97pub enum GuestBinary<'a> {
98    /// A buffer containing the GuestBinary
99    Buffer(&'a [u8]),
100    /// A path to the GuestBinary
101    FilePath(String),
102}
103
104/// A `GuestBlob` containing data and the permissions for its use.
105#[derive(Debug)]
106pub struct GuestBlob<'a> {
107    /// The data contained in the blob.
108    pub data: &'a [u8],
109    /// The permissions for the blob in memory.
110    /// By default, it's READ
111    pub permissions: MemoryRegionFlags,
112}
113
114impl<'a> From<&'a [u8]> for GuestBlob<'a> {
115    fn from(data: &'a [u8]) -> Self {
116        GuestBlob {
117            data,
118            permissions: DEFAULT_GUEST_BLOB_MEM_FLAGS,
119        }
120    }
121}
122
123/// Container for a guest binary and optional initialization data.
124///
125/// This struct combines a guest binary (either from a file or memory buffer) with
126/// optional data that will be available to the guest during execution.
127#[derive(Debug)]
128pub struct GuestEnvironment<'a, 'b> {
129    /// The guest binary, which can be a file path or a buffer.
130    pub guest_binary: GuestBinary<'a>,
131    /// An optional guest blob, which can be used to provide additional data to the guest.
132    pub init_data: Option<GuestBlob<'b>>,
133}
134
135impl<'a, 'b> GuestEnvironment<'a, 'b> {
136    /// Creates a new `GuestEnvironment` with the given guest binary and an optional guest blob.
137    pub fn new(guest_binary: GuestBinary<'a>, init_data: Option<&'b [u8]>) -> Self {
138        GuestEnvironment {
139            guest_binary,
140            init_data: init_data.map(GuestBlob::from),
141        }
142    }
143}
144
145impl<'a> From<GuestBinary<'a>> for GuestEnvironment<'a, '_> {
146    fn from(guest_binary: GuestBinary<'a>) -> Self {
147        GuestEnvironment {
148            guest_binary,
149            init_data: None,
150        }
151    }
152}
153
154impl UninitializedSandbox {
155    /// Creates a new uninitialized sandbox for the given guest environment.
156    ///
157    /// The guest binary can be provided as either a file path or memory buffer.
158    /// An optional configuration can customize memory sizes and sandbox settings.
159    /// After creation, register host functions using [`register`](Self::register)
160    /// before calling [`evolve`](Self::evolve) to complete initialization and create the VM.
161    #[instrument(
162        err(Debug),
163        skip(env),
164        parent = Span::current()
165    )]
166    pub fn new<'a, 'b>(
167        env: impl Into<GuestEnvironment<'a, 'b>>,
168        cfg: Option<SandboxConfiguration>,
169    ) -> Result<Self> {
170        #[cfg(feature = "build-metadata")]
171        log_build_details();
172
173        // hyperlight is only supported on Windows 11 and Windows Server 2022 and later
174        #[cfg(target_os = "windows")]
175        check_windows_version()?;
176
177        let env: GuestEnvironment<'_, '_> = env.into();
178        let guest_binary = env.guest_binary;
179        let guest_blob = env.init_data;
180
181        // If the guest binary is a file make sure it exists
182        let guest_binary = match guest_binary {
183            GuestBinary::FilePath(binary_path) => {
184                let path = Path::new(&binary_path)
185                    .canonicalize()
186                    .map_err(|e| new_error!("GuestBinary not found: '{}': {}", binary_path, e))?
187                    .into_os_string()
188                    .into_string()
189                    .map_err(|e| new_error!("Error converting OsString to String: {:?}", e))?;
190
191                GuestBinary::FilePath(path)
192            }
193            buffer @ GuestBinary::Buffer(_) => buffer,
194        };
195
196        let sandbox_cfg = cfg.unwrap_or_default();
197
198        #[cfg(any(crashdump, gdb))]
199        let rt_cfg = {
200            #[cfg(crashdump)]
201            let guest_core_dump = sandbox_cfg.get_guest_core_dump();
202
203            #[cfg(gdb)]
204            let debug_info = sandbox_cfg.get_guest_debug_info();
205
206            #[cfg(crashdump)]
207            let binary_path = if let GuestBinary::FilePath(ref path) = guest_binary {
208                Some(path.clone())
209            } else {
210                None
211            };
212
213            SandboxRuntimeConfig {
214                #[cfg(crashdump)]
215                binary_path,
216                #[cfg(gdb)]
217                debug_info,
218                #[cfg(crashdump)]
219                guest_core_dump,
220            }
221        };
222
223        let (mut mem_mgr_wrapper, load_info) = UninitializedSandbox::load_guest_binary(
224            sandbox_cfg,
225            &guest_binary,
226            guest_blob.as_ref(),
227        )?;
228
229        mem_mgr_wrapper.write_memory_layout()?;
230
231        // if env has a guest blob, load it into shared mem
232        if let Some(blob) = guest_blob {
233            mem_mgr_wrapper.write_init_data(blob.data)?;
234        }
235
236        let host_funcs = Arc::new(Mutex::new(FunctionRegistry::default()));
237
238        let mut sandbox = Self {
239            host_funcs,
240            mgr: mem_mgr_wrapper,
241            max_guest_log_level: None,
242            config: sandbox_cfg,
243            #[cfg(any(crashdump, gdb))]
244            rt_cfg,
245            load_info,
246        };
247
248        // If we were passed a writer for host print register it otherwise use the default.
249        sandbox.register_print(default_writer_func)?;
250
251        crate::debug!("Sandbox created:  {:#?}", sandbox);
252
253        Ok(sandbox)
254    }
255
256    /// Creates and initializes the virtual machine, transforming this into a ready-to-use sandbox.
257    ///
258    /// This method consumes the `UninitializedSandbox` and performs the final initialization
259    /// steps to create the underlying virtual machine. Once evolved, the resulting
260    /// [`MultiUseSandbox`] can execute guest code and handle function calls.
261    #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
262    pub fn evolve(self) -> Result<MultiUseSandbox> {
263        evolve_impl_multi_use(self)
264    }
265
266    /// Load the file at `bin_path_str` into a PE file, then attempt to
267    /// load the PE file into a `SandboxMemoryManager` and return it.
268    ///
269    /// If `run_from_guest_binary` is passed as `true`, and this code is
270    /// running on windows, this function will call
271    /// `SandboxMemoryManager::load_guest_binary_using_load_library` to
272    /// create the new `SandboxMemoryManager`. If `run_from_guest_binary` is
273    /// passed as `true` and we're not running on windows, this function will
274    /// return an `Err`. Otherwise, if `run_from_guest_binary` is passed
275    /// as `false`, this function calls `SandboxMemoryManager::load_guest_binary_into_memory`.
276    #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
277    pub(super) fn load_guest_binary(
278        cfg: SandboxConfiguration,
279        guest_binary: &GuestBinary,
280        guest_blob: Option<&GuestBlob>,
281    ) -> Result<(
282        SandboxMemoryManager<ExclusiveSharedMemory>,
283        crate::mem::exe::LoadInfo,
284    )> {
285        let exe_info = match guest_binary {
286            GuestBinary::FilePath(bin_path_str) => ExeInfo::from_file(bin_path_str)?,
287            GuestBinary::Buffer(buffer) => ExeInfo::from_buf(buffer)?,
288        };
289
290        SandboxMemoryManager::load_guest_binary_into_memory(cfg, exe_info, guest_blob)
291    }
292
293    /// Sets the maximum log level for guest code execution.
294    ///
295    /// If not set, the log level is determined by the `RUST_LOG` environment variable,
296    /// defaulting to [`LevelFilter::Error`] if unset.
297    pub fn set_max_guest_log_level(&mut self, log_level: LevelFilter) {
298        self.max_guest_log_level = Some(log_level);
299    }
300
301    /// Registers a host function that the guest can call.
302    pub fn register<Args: ParameterTuple, Output: SupportedReturnType>(
303        &mut self,
304        name: impl AsRef<str>,
305        host_func: impl Into<HostFunction<Output, Args>>,
306    ) -> Result<()> {
307        register_host_function(host_func, self, name.as_ref(), None)
308    }
309
310    /// Registers a host function with additional allowed syscalls during execution.
311    ///
312    /// Unlike [`register`](Self::register), this variant allows specifying extra syscalls
313    /// that will be permitted when the function handler runs.
314    #[cfg(seccomp)]
315    pub fn register_with_extra_allowed_syscalls<
316        Args: ParameterTuple,
317        Output: SupportedReturnType,
318    >(
319        &mut self,
320        name: impl AsRef<str>,
321        host_func: impl Into<HostFunction<Output, Args>>,
322        extra_allowed_syscalls: impl IntoIterator<Item = crate::sandbox::ExtraAllowedSyscall>,
323    ) -> Result<()> {
324        let extra_allowed_syscalls: Vec<_> = extra_allowed_syscalls.into_iter().collect();
325        register_host_function(host_func, self, name.as_ref(), Some(extra_allowed_syscalls))
326    }
327
328    /// Registers the special "HostPrint" function for guest printing.
329    ///
330    /// This overrides the default behavior of writing to stdout.
331    /// The function expects the signature `FnMut(String) -> i32`
332    /// and will be called when the guest wants to print output.
333    pub fn register_print(
334        &mut self,
335        print_func: impl Into<HostFunction<i32, (String,)>>,
336    ) -> Result<()> {
337        #[cfg(not(seccomp))]
338        self.register("HostPrint", print_func)?;
339
340        #[cfg(seccomp)]
341        self.register_with_extra_allowed_syscalls(
342            "HostPrint",
343            print_func,
344            EXTRA_ALLOWED_SYSCALLS_FOR_WRITER_FUNC.iter().copied(),
345        )?;
346
347        Ok(())
348    }
349
350    /// Registers the "HostPrint" function with additional allowed syscalls.
351    ///
352    /// Like [`register_print`](Self::register_print), but allows specifying extra syscalls
353    /// that will be permitted during function execution.
354    #[cfg(seccomp)]
355    pub fn register_print_with_extra_allowed_syscalls(
356        &mut self,
357        print_func: impl Into<HostFunction<i32, (String,)>>,
358        extra_allowed_syscalls: impl IntoIterator<Item = crate::sandbox::ExtraAllowedSyscall>,
359    ) -> Result<()> {
360        #[cfg(seccomp)]
361        self.register_with_extra_allowed_syscalls(
362            "HostPrint",
363            print_func,
364            EXTRA_ALLOWED_SYSCALLS_FOR_WRITER_FUNC
365                .iter()
366                .copied()
367                .chain(extra_allowed_syscalls),
368        )?;
369
370        Ok(())
371    }
372}
373// Check to see if the current version of Windows is supported
374// Hyperlight is only supported on Windows 11 and Windows Server 2022 and later
375#[cfg(target_os = "windows")]
376fn check_windows_version() -> Result<()> {
377    use windows_version::{OsVersion, is_server};
378    const WINDOWS_MAJOR: u32 = 10;
379    const WINDOWS_MINOR: u32 = 0;
380    const WINDOWS_PACK: u32 = 0;
381
382    // Windows Server 2022 has version numbers 10.0.20348 or greater
383    if is_server() {
384        if OsVersion::current() < OsVersion::new(WINDOWS_MAJOR, WINDOWS_MINOR, WINDOWS_PACK, 20348)
385        {
386            return Err(new_error!(
387                "Hyperlight Requires Windows Server 2022 or newer"
388            ));
389        }
390    } else if OsVersion::current()
391        < OsVersion::new(WINDOWS_MAJOR, WINDOWS_MINOR, WINDOWS_PACK, 22000)
392    {
393        return Err(new_error!("Hyperlight Requires Windows 11 or newer"));
394    }
395    Ok(())
396}
397
398#[cfg(test)]
399mod tests {
400    use std::sync::Arc;
401    use std::sync::mpsc::channel;
402    use std::{fs, thread};
403
404    use crossbeam_queue::ArrayQueue;
405    use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnValue};
406    use hyperlight_testing::simple_guest_as_string;
407
408    use crate::sandbox::SandboxConfiguration;
409    use crate::sandbox::uninitialized::{GuestBinary, GuestEnvironment};
410    use crate::{MultiUseSandbox, Result, UninitializedSandbox, new_error};
411
412    #[test]
413    fn test_load_extra_blob() {
414        let binary_path = simple_guest_as_string().unwrap();
415        let buffer = [0xde, 0xad, 0xbe, 0xef];
416        let guest_env =
417            GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), Some(&buffer));
418
419        let uninitialized_sandbox = UninitializedSandbox::new(guest_env, None).unwrap();
420        let mut sandbox: MultiUseSandbox = uninitialized_sandbox.evolve().unwrap();
421
422        let res = sandbox
423            .call::<Vec<u8>>("ReadFromUserMemory", (4u64, buffer.to_vec()))
424            .expect("Failed to call ReadFromUserMemory");
425
426        assert_eq!(res, buffer.to_vec());
427    }
428
429    #[test]
430    fn test_new_sandbox() {
431        // Guest Binary exists at path
432
433        let binary_path = simple_guest_as_string().unwrap();
434        let sandbox = UninitializedSandbox::new(GuestBinary::FilePath(binary_path.clone()), None);
435        assert!(sandbox.is_ok());
436
437        // Guest Binary does not exist at path
438
439        let mut binary_path_does_not_exist = binary_path.clone();
440        binary_path_does_not_exist.push_str(".nonexistent");
441        let uninitialized_sandbox =
442            UninitializedSandbox::new(GuestBinary::FilePath(binary_path_does_not_exist), None);
443        assert!(uninitialized_sandbox.is_err());
444
445        // Non default memory configuration
446        let cfg = {
447            let mut cfg = SandboxConfiguration::default();
448            cfg.set_input_data_size(0x1000);
449            cfg.set_output_data_size(0x1000);
450            cfg.set_stack_size(0x1000);
451            cfg.set_heap_size(0x1000);
452            Some(cfg)
453        };
454
455        let uninitialized_sandbox =
456            UninitializedSandbox::new(GuestBinary::FilePath(binary_path.clone()), cfg);
457        assert!(uninitialized_sandbox.is_ok());
458
459        let uninitialized_sandbox =
460            UninitializedSandbox::new(GuestBinary::FilePath(binary_path), None).unwrap();
461
462        // Get a Sandbox from an uninitialized sandbox without a call back function
463
464        let _sandbox: MultiUseSandbox = uninitialized_sandbox.evolve().unwrap();
465
466        // Test with a valid guest binary buffer
467
468        let binary_path = simple_guest_as_string().unwrap();
469        let sandbox =
470            UninitializedSandbox::new(GuestBinary::Buffer(&fs::read(binary_path).unwrap()), None);
471        assert!(sandbox.is_ok());
472
473        // Test with a invalid guest binary buffer
474
475        let binary_path = simple_guest_as_string().unwrap();
476        let mut bytes = fs::read(binary_path).unwrap();
477        let _ = bytes.split_off(100);
478        let sandbox = UninitializedSandbox::new(GuestBinary::Buffer(&bytes), None);
479        assert!(sandbox.is_err());
480    }
481
482    #[test]
483    fn test_load_guest_binary_manual() {
484        let cfg = SandboxConfiguration::default();
485
486        let simple_guest_path = simple_guest_as_string().unwrap();
487
488        UninitializedSandbox::load_guest_binary(
489            cfg,
490            &GuestBinary::FilePath(simple_guest_path),
491            None.as_ref(),
492        )
493        .unwrap();
494    }
495
496    #[test]
497    fn test_host_functions() {
498        let uninitialized_sandbox = || {
499            UninitializedSandbox::new(
500                GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
501                None,
502            )
503            .unwrap()
504        };
505
506        // simple register + call
507        {
508            let mut usbox = uninitialized_sandbox();
509
510            usbox.register("test0", |arg: i32| Ok(arg + 1)).unwrap();
511
512            let sandbox: Result<MultiUseSandbox> = usbox.evolve();
513            assert!(sandbox.is_ok());
514            let sandbox = sandbox.unwrap();
515
516            let host_funcs = sandbox
517                ._host_funcs
518                .try_lock()
519                .map_err(|_| new_error!("Error locking"));
520
521            assert!(host_funcs.is_ok());
522
523            let res = host_funcs
524                .unwrap()
525                .call_host_function("test0", vec![ParameterValue::Int(1)])
526                .unwrap();
527
528            assert_eq!(res, ReturnValue::Int(2));
529        }
530
531        // multiple parameters register + call
532        {
533            let mut usbox = uninitialized_sandbox();
534
535            usbox.register("test1", |a: i32, b: i32| Ok(a + b)).unwrap();
536
537            let sandbox: Result<MultiUseSandbox> = usbox.evolve();
538            assert!(sandbox.is_ok());
539            let sandbox = sandbox.unwrap();
540
541            let host_funcs = sandbox
542                ._host_funcs
543                .try_lock()
544                .map_err(|_| new_error!("Error locking"));
545
546            assert!(host_funcs.is_ok());
547
548            let res = host_funcs
549                .unwrap()
550                .call_host_function(
551                    "test1",
552                    vec![ParameterValue::Int(1), ParameterValue::Int(2)],
553                )
554                .unwrap();
555
556            assert_eq!(res, ReturnValue::Int(3));
557        }
558
559        // incorrect arguments register + call
560        {
561            let mut usbox = uninitialized_sandbox();
562
563            usbox
564                .register("test2", |msg: String| {
565                    println!("test2 called: {}", msg);
566                    Ok(())
567                })
568                .unwrap();
569
570            let sandbox: Result<MultiUseSandbox> = usbox.evolve();
571            assert!(sandbox.is_ok());
572            let sandbox = sandbox.unwrap();
573
574            let host_funcs = sandbox
575                ._host_funcs
576                .try_lock()
577                .map_err(|_| new_error!("Error locking"));
578
579            assert!(host_funcs.is_ok());
580
581            let res = host_funcs.unwrap().call_host_function("test2", vec![]);
582            assert!(res.is_err());
583        }
584
585        // calling a function that doesn't exist
586        {
587            let usbox = uninitialized_sandbox();
588            let sandbox: Result<MultiUseSandbox> = usbox.evolve();
589            assert!(sandbox.is_ok());
590            let sandbox = sandbox.unwrap();
591
592            let host_funcs = sandbox
593                ._host_funcs
594                .try_lock()
595                .map_err(|_| new_error!("Error locking"));
596
597            assert!(host_funcs.is_ok());
598
599            let res = host_funcs.unwrap().call_host_function("test4", vec![]);
600            assert!(res.is_err());
601        }
602    }
603
604    #[test]
605    fn test_host_print() {
606        // writer as a FnMut closure mutating a captured variable and then trying to access the captured variable
607        // after the Sandbox instance has been dropped
608        // this example is fairly contrived but we should still support such an approach.
609
610        let (tx, rx) = channel();
611
612        let writer = move |msg| {
613            let _ = tx.send(msg);
614            Ok(0)
615        };
616
617        let mut sandbox = UninitializedSandbox::new(
618            GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
619            None,
620        )
621        .expect("Failed to create sandbox");
622
623        sandbox
624            .register_print(writer)
625            .expect("Failed to register host print function");
626
627        let host_funcs = sandbox
628            .host_funcs
629            .try_lock()
630            .map_err(|_| new_error!("Error locking"));
631
632        assert!(host_funcs.is_ok());
633
634        host_funcs.unwrap().host_print("test".to_string()).unwrap();
635
636        drop(sandbox);
637
638        let received_msgs: Vec<_> = rx.into_iter().collect();
639        assert_eq!(received_msgs, ["test"]);
640
641        // There may be cases where a mutable reference to the captured variable is not required to be used outside the closure
642        // e.g. if the function is writing to a file or a socket etc.
643
644        // writer as a FnMut closure mutating a captured variable but not trying to access the captured variable
645
646        // This seems more realistic as the client is creating a file to be written to in the closure
647        // and then accessing the file a different handle.
648        // The problem is that captured_file still needs static lifetime so even though we can access the data through the second file handle
649        // this still does not work as the captured_file is dropped at the end of the function
650
651        // TODO: Currently, we block any writes that are not to
652        // the stdout/stderr file handles, so this code is commented
653        // out until we can register writer functions like any other
654        // host functions with their own set of extra allowed syscalls.
655        // In particular, this code should be brought back once we have addressed the issue
656
657        // let captured_file = Arc::new(Mutex::new(NamedTempFile::new().unwrap()));
658        // let capture_file_clone = captured_file.clone();
659        //
660        // let capture_file_lock = captured_file
661        //     .try_lock()
662        //     .map_err(|_| new_error!("Error locking"))
663        //     .unwrap();
664        // let mut file = capture_file_lock.reopen().unwrap();
665        // drop(capture_file_lock);
666        //
667        // let writer = move |msg: String| -> Result<i32> {
668        //     let mut captured_file = capture_file_clone
669        //         .try_lock()
670        //         .map_err(|_| new_error!("Error locking"))
671        //         .unwrap();
672        //     captured_file.write_all(msg.as_bytes()).unwrap();
673        //     Ok(0)
674        // };
675        //
676        // let writer_func = Arc::new(Mutex::new(writer));
677        //
678        // let sandbox = UninitializedSandbox::new(
679        //     GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
680        //     None,
681        //     None,
682        //     Some(&writer_func),
683        // )
684        // .expect("Failed to create sandbox");
685        //
686        // let host_funcs = sandbox
687        //     .host_funcs
688        //     .try_lock()
689        //     .map_err(|_| new_error!("Error locking"));
690        //
691        // assert!(host_funcs.is_ok());
692        //
693        // host_funcs.unwrap().host_print("test2".to_string()).unwrap();
694        //
695        // let mut buffer = String::new();
696        // file.read_to_string(&mut buffer).unwrap();
697        // assert_eq!(buffer, "test2");
698
699        // writer as a function
700
701        fn fn_writer(msg: String) -> Result<i32> {
702            assert_eq!(msg, "test2");
703            Ok(0)
704        }
705
706        let mut sandbox = UninitializedSandbox::new(
707            GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
708            None,
709        )
710        .expect("Failed to create sandbox");
711
712        sandbox
713            .register_print(fn_writer)
714            .expect("Failed to register host print function");
715
716        let host_funcs = sandbox
717            .host_funcs
718            .try_lock()
719            .map_err(|_| new_error!("Error locking"));
720
721        assert!(host_funcs.is_ok());
722
723        host_funcs.unwrap().host_print("test2".to_string()).unwrap();
724
725        // writer as a method
726
727        let mut test_host_print = TestHostPrint::new();
728
729        // create a closure over the struct method
730
731        let writer_closure = move |s| test_host_print.write(s);
732
733        let mut sandbox = UninitializedSandbox::new(
734            GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
735            None,
736        )
737        .expect("Failed to create sandbox");
738
739        sandbox
740            .register_print(writer_closure)
741            .expect("Failed to register host print function");
742
743        let host_funcs = sandbox
744            .host_funcs
745            .try_lock()
746            .map_err(|_| new_error!("Error locking"));
747
748        assert!(host_funcs.is_ok());
749
750        host_funcs.unwrap().host_print("test3".to_string()).unwrap();
751    }
752
753    struct TestHostPrint {}
754
755    impl TestHostPrint {
756        fn new() -> Self {
757            TestHostPrint {}
758        }
759
760        fn write(&mut self, msg: String) -> Result<i32> {
761            assert_eq!(msg, "test3");
762            Ok(0)
763        }
764    }
765
766    #[test]
767    fn check_create_and_use_sandbox_on_different_threads() {
768        let unintializedsandbox_queue = Arc::new(ArrayQueue::<UninitializedSandbox>::new(10));
769        let sandbox_queue = Arc::new(ArrayQueue::<MultiUseSandbox>::new(10));
770
771        for i in 0..10 {
772            let simple_guest_path = simple_guest_as_string().expect("Guest Binary Missing");
773            let unintializedsandbox = {
774                let err_string = format!("failed to create UninitializedSandbox {i}");
775                let err_str = err_string.as_str();
776                UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_path), None)
777                    .expect(err_str)
778            };
779
780            {
781                let err_string = format!("Failed to push UninitializedSandbox {i}");
782                let err_str = err_string.as_str();
783
784                unintializedsandbox_queue
785                    .push(unintializedsandbox)
786                    .expect(err_str);
787            }
788        }
789
790        let thread_handles = (0..10)
791            .map(|i| {
792                let uq = unintializedsandbox_queue.clone();
793                let sq = sandbox_queue.clone();
794                thread::spawn(move || {
795                    let uninitialized_sandbox = uq.pop().unwrap_or_else(|| {
796                        panic!("Failed to pop UninitializedSandbox thread {}", i)
797                    });
798
799                    let host_funcs = uninitialized_sandbox
800                        .host_funcs
801                        .try_lock()
802                        .map_err(|_| new_error!("Error locking"));
803
804                    assert!(host_funcs.is_ok());
805
806                    host_funcs
807                        .unwrap()
808                        .host_print(format!("Print from UninitializedSandbox on Thread {}\n", i))
809                        .unwrap();
810
811                    let sandbox = uninitialized_sandbox.evolve().unwrap_or_else(|_| {
812                        panic!("Failed to initialize UninitializedSandbox thread {}", i)
813                    });
814
815                    sq.push(sandbox).unwrap_or_else(|_| {
816                        panic!("Failed to push UninitializedSandbox thread {}", i)
817                    })
818                })
819            })
820            .collect::<Vec<_>>();
821
822        for handle in thread_handles {
823            handle.join().unwrap();
824        }
825
826        let thread_handles = (0..10)
827            .map(|i| {
828                let sq = sandbox_queue.clone();
829                thread::spawn(move || {
830                    let sandbox = sq
831                        .pop()
832                        .unwrap_or_else(|| panic!("Failed to pop Sandbox thread {}", i));
833
834                    let host_funcs = sandbox
835                        ._host_funcs
836                        .try_lock()
837                        .map_err(|_| new_error!("Error locking"));
838
839                    assert!(host_funcs.is_ok());
840
841                    host_funcs
842                        .unwrap()
843                        .host_print(format!("Print from Sandbox on Thread {}\n", i))
844                        .unwrap();
845                })
846            })
847            .collect::<Vec<_>>();
848
849        for handle in thread_handles {
850            handle.join().unwrap();
851        }
852    }
853
854    #[test]
855    // Tests that trace data are emitted when a trace subscriber is set
856    // this test is ignored because it is incompatible with other tests , specifically those which require a logger for tracing
857    // marking  this test as ignored means that running `cargo test` will not run this test but will allow a developer who runs that command
858    // from their workstation to be successful without needed to know about test interdependencies
859    // this test will be run explicitly as a part of the CI pipeline
860    #[ignore]
861    #[cfg(feature = "build-metadata")]
862    fn test_trace_trace() {
863        use hyperlight_testing::logger::Logger as TestLogger;
864        use hyperlight_testing::tracing_subscriber::TracingSubscriber as TestSubscriber;
865        use serde_json::{Map, Value};
866        use tracing::Level as tracing_level;
867        use tracing_core::Subscriber;
868        use tracing_core::callsite::rebuild_interest_cache;
869        use uuid::Uuid;
870
871        use crate::testing::log_values::build_metadata_testing::try_to_strings;
872        use crate::testing::log_values::test_value_as_str;
873
874        TestLogger::initialize_log_tracer();
875        rebuild_interest_cache();
876        let subscriber = TestSubscriber::new(tracing_level::TRACE);
877        tracing::subscriber::with_default(subscriber.clone(), || {
878            let correlation_id = Uuid::new_v4().as_hyphenated().to_string();
879            let span = tracing::error_span!("test_trace_logs", correlation_id).entered();
880
881            // We should be in span 1
882
883            let current_span = subscriber.current_span();
884            assert!(current_span.is_known(), "Current span is unknown");
885            let current_span_metadata = current_span.into_inner().unwrap();
886            assert_eq!(
887                current_span_metadata.0.into_u64(),
888                1,
889                "Current span is not span 1"
890            );
891            assert_eq!(current_span_metadata.1.name(), "test_trace_logs");
892
893            // Get the span data and check the correlation id
894
895            let span_data = subscriber.get_span(1);
896            let span_attributes: &Map<String, Value> = span_data
897                .get("span")
898                .unwrap()
899                .get("attributes")
900                .unwrap()
901                .as_object()
902                .unwrap();
903
904            test_value_as_str(span_attributes, "correlation_id", correlation_id.as_str());
905
906            let mut binary_path = simple_guest_as_string().unwrap();
907            binary_path.push_str("does_not_exist");
908
909            let sbox = UninitializedSandbox::new(GuestBinary::FilePath(binary_path), None);
910            assert!(sbox.is_err());
911
912            // Now we should still be in span 1 but span 2 should be created (we created entered and exited span 2 when we called UninitializedSandbox::new)
913
914            let current_span = subscriber.current_span();
915            assert!(current_span.is_known(), "Current span is unknown");
916            let current_span_metadata = current_span.into_inner().unwrap();
917            assert_eq!(
918                current_span_metadata.0.into_u64(),
919                1,
920                "Current span is not span 1"
921            );
922
923            let span_metadata = subscriber.get_span_metadata(2);
924            assert_eq!(span_metadata.name(), "new");
925
926            // There should be one event for the error that the binary path does not exist plus 14 info events for the logging of the crate info
927
928            let events = subscriber.get_events();
929            assert_eq!(events.len(), 15);
930
931            let mut count_matching_events = 0;
932
933            for json_value in events {
934                let event_values = json_value.as_object().unwrap().get("event").unwrap();
935                let metadata_values_map =
936                    event_values.get("metadata").unwrap().as_object().unwrap();
937                let event_values_map = event_values.as_object().unwrap();
938
939                let expected_error_start = "Error(\"GuestBinary not found:";
940
941                let err_vals_res = try_to_strings([
942                    (metadata_values_map, "level"),
943                    (event_values_map, "error"),
944                    (metadata_values_map, "module_path"),
945                    (metadata_values_map, "target"),
946                ]);
947                if let Ok(err_vals) = err_vals_res
948                    && err_vals[0] == "ERROR"
949                    && err_vals[1].starts_with(expected_error_start)
950                    && err_vals[2] == "hyperlight_host::sandbox::uninitialized"
951                    && err_vals[3] == "hyperlight_host::sandbox::uninitialized"
952                {
953                    count_matching_events += 1;
954                }
955            }
956            assert!(
957                count_matching_events == 1,
958                "Unexpected number of matching events {}",
959                count_matching_events
960            );
961            span.exit();
962            subscriber.clear();
963        });
964    }
965
966    #[test]
967    #[ignore]
968    // Tests that traces are emitted as log records when there is no trace
969    // subscriber configured.
970    #[cfg(feature = "build-metadata")]
971    fn test_log_trace() {
972        use std::path::PathBuf;
973
974        use hyperlight_testing::logger::{LOGGER as TEST_LOGGER, Logger as TestLogger};
975        use log::Level;
976        use tracing_core::callsite::rebuild_interest_cache;
977
978        {
979            TestLogger::initialize_test_logger();
980            TEST_LOGGER.set_max_level(log::LevelFilter::Trace);
981
982            // This makes sure that the metadata interest cache is rebuilt so that
983            // the log records are emitted for the trace records
984
985            rebuild_interest_cache();
986
987            let mut invalid_binary_path = simple_guest_as_string().unwrap();
988            invalid_binary_path.push_str("does_not_exist");
989
990            let sbox = UninitializedSandbox::new(GuestBinary::FilePath(invalid_binary_path), None);
991            assert!(sbox.is_err());
992
993            // When tracing is creating log records it will create a log
994            // record for the creation of the span (from the instrument
995            // attribute), and will then create a log record for the entry to
996            // and exit from the span.
997            //
998            // It also creates a log record for the span being dropped.
999            //
1000            // In addition there are 14 info log records created for build information
1001            //
1002            // So we expect 19 log records for this test, four for the span and
1003            // then one for the error as the file that we are attempting to
1004            // load into the sandbox does not exist, plus the 14 info log records
1005
1006            let num_calls = TEST_LOGGER.num_log_calls();
1007            assert_eq!(19, num_calls);
1008
1009            // Log record 1
1010
1011            let logcall = TEST_LOGGER.get_log_call(0).unwrap();
1012            assert_eq!(Level::Info, logcall.level);
1013
1014            assert!(logcall.args.starts_with("new; cfg"));
1015            assert_eq!("hyperlight_host::sandbox::uninitialized", logcall.target);
1016
1017            // Log record 2
1018
1019            let logcall = TEST_LOGGER.get_log_call(1).unwrap();
1020            assert_eq!(Level::Trace, logcall.level);
1021            assert_eq!(logcall.args, "-> new;");
1022            assert_eq!("tracing::span::active", logcall.target);
1023
1024            // Log record 17
1025
1026            let logcall = TEST_LOGGER.get_log_call(16).unwrap();
1027            assert_eq!(Level::Error, logcall.level);
1028            assert!(
1029                logcall
1030                    .args
1031                    .starts_with("error=Error(\"GuestBinary not found:")
1032            );
1033            assert_eq!("hyperlight_host::sandbox::uninitialized", logcall.target);
1034
1035            // Log record 18
1036
1037            let logcall = TEST_LOGGER.get_log_call(17).unwrap();
1038            assert_eq!(Level::Trace, logcall.level);
1039            assert_eq!(logcall.args, "<- new;");
1040            assert_eq!("tracing::span::active", logcall.target);
1041
1042            // Log record 19
1043
1044            let logcall = TEST_LOGGER.get_log_call(18).unwrap();
1045            assert_eq!(Level::Trace, logcall.level);
1046            assert_eq!(logcall.args, "-- new;");
1047            assert_eq!("tracing::span", logcall.target);
1048        }
1049        {
1050            // test to ensure an invalid binary logs & traces properly
1051            TEST_LOGGER.clear_log_calls();
1052            TEST_LOGGER.set_max_level(log::LevelFilter::Info);
1053
1054            let mut valid_binary_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1055            valid_binary_path.push("src");
1056            valid_binary_path.push("sandbox");
1057            valid_binary_path.push("initialized.rs");
1058
1059            let sbox = UninitializedSandbox::new(
1060                GuestBinary::FilePath(valid_binary_path.into_os_string().into_string().unwrap()),
1061                None,
1062            );
1063            assert!(sbox.is_err());
1064
1065            // There should be 2 calls this time when we change to the log
1066            // LevelFilter to Info.
1067            let num_calls = TEST_LOGGER.num_log_calls();
1068            assert_eq!(2, num_calls);
1069
1070            // Log record 1
1071
1072            let logcall = TEST_LOGGER.get_log_call(0).unwrap();
1073            assert_eq!(Level::Info, logcall.level);
1074
1075            assert!(logcall.args.starts_with("new; cfg"));
1076            assert_eq!("hyperlight_host::sandbox::uninitialized", logcall.target);
1077
1078            // Log record 2
1079
1080            let logcall = TEST_LOGGER.get_log_call(1).unwrap();
1081            assert_eq!(Level::Error, logcall.level);
1082            assert!(
1083                logcall
1084                    .args
1085                    .starts_with("error=Error(\"GuestBinary not found:")
1086            );
1087            assert_eq!("hyperlight_host::sandbox::uninitialized", logcall.target);
1088        }
1089        {
1090            TEST_LOGGER.clear_log_calls();
1091            TEST_LOGGER.set_max_level(log::LevelFilter::Error);
1092
1093            let sbox = {
1094                let res = UninitializedSandbox::new(
1095                    GuestBinary::FilePath(simple_guest_as_string().unwrap()),
1096                    None,
1097                );
1098                res.unwrap()
1099            };
1100            let _: Result<MultiUseSandbox> = sbox.evolve();
1101
1102            let num_calls = TEST_LOGGER.num_log_calls();
1103
1104            assert_eq!(0, num_calls);
1105        }
1106    }
1107
1108    #[test]
1109    fn test_invalid_path() {
1110        let invalid_path = "some/path/that/does/not/exist";
1111        let sbox = UninitializedSandbox::new(GuestBinary::FilePath(invalid_path.to_string()), None);
1112        println!("{:?}", sbox);
1113        #[cfg(target_os = "windows")]
1114        assert!(
1115            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)"))
1116        );
1117        #[cfg(target_os = "linux")]
1118        assert!(
1119            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)"))
1120        );
1121    }
1122}