hyperlight_host/sandbox/
uninitialized.rs

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