hyperlight_host/sandbox/
mod.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
17/// Configuration needed to establish a sandbox.
18pub mod config;
19/// Functionality for reading, but not modifying host functions
20pub(crate) mod host_funcs;
21/// Functionality for dealing with `Sandbox`es that contain Hypervisors
22pub(crate) mod hypervisor;
23/// Functionality for dealing with initialized sandboxes that can
24/// call 0 or more guest functions
25pub mod initialized_multi_use;
26/// Functionality for interacting with a sandbox's internally-stored
27/// `SandboxMemoryManager`
28pub(crate) mod mem_mgr;
29pub(crate) mod outb;
30/// Functionality for creating uninitialized sandboxes, manipulating them,
31/// and converting them to initialized sandboxes.
32pub mod uninitialized;
33/// Functionality for properly converting `UninitializedSandbox`es to
34/// initialized `Sandbox`es.
35pub(crate) mod uninitialized_evolve;
36
37/// Representation of a snapshot of a `Sandbox`.
38pub mod snapshot;
39
40/// Trait used by the macros to paper over the differences between hyperlight and hyperlight-wasm
41mod callable;
42
43#[cfg(feature = "unwind_guest")]
44use std::io::Write;
45#[cfg(feature = "trace_guest")]
46use std::sync::{Arc, Mutex};
47
48/// Trait used by the macros to paper over the differences between hyperlight and hyperlight-wasm
49pub use callable::Callable;
50/// Re-export for `SandboxConfiguration` type
51pub use config::SandboxConfiguration;
52#[cfg(feature = "unwind_guest")]
53use framehop::Unwinder;
54/// Re-export for the `MultiUseSandbox` type
55pub use initialized_multi_use::MultiUseSandbox;
56use tracing::{Span, instrument};
57/// Re-export for `GuestBinary` type
58pub use uninitialized::GuestBinary;
59/// Re-export for `UninitializedSandbox` type
60pub use uninitialized::UninitializedSandbox;
61
62use self::mem_mgr::MemMgrWrapper;
63#[cfg(target_os = "windows")]
64use crate::hypervisor::windows_hypervisor_platform;
65use crate::mem::shared_mem::HostSharedMemory;
66
67// In case its not obvious why there are separate is_supported_platform and is_hypervisor_present functions its because
68// Hyperlight is designed to be able to run on a host that doesn't have a hypervisor.
69// In that case, the sandbox will be in process, we plan on making this a dev only feature and fixing up Linux support
70// so we should review the need for this function at that time.
71
72/// Determine if this is a supported platform for Hyperlight
73///
74/// Returns a boolean indicating whether this is a supported platform.
75#[instrument(skip_all, parent = Span::current())]
76pub fn is_supported_platform() -> bool {
77    #[cfg(not(target_os = "linux"))]
78    #[cfg(not(target_os = "windows"))]
79    return false;
80
81    true
82}
83
84/// Alias for the type of extra allowed syscalls.
85pub type ExtraAllowedSyscall = i64;
86
87/// Determine whether a suitable hypervisor is available to run
88/// this sandbox.
89///
90///  Returns a boolean indicating whether a suitable hypervisor is present.
91#[instrument(skip_all, parent = Span::current())]
92pub fn is_hypervisor_present() -> bool {
93    hypervisor::get_available_hypervisor().is_some()
94}
95
96#[cfg(feature = "trace_guest")]
97#[derive(Clone)]
98/// The information that trace collection requires in order to write
99/// an accurate trace.
100pub(crate) struct TraceInfo {
101    /// The epoch against which trace events are timed; at least as
102    /// early as the creation of the sandbox being traced.
103    pub epoch: std::time::Instant,
104    /// The frequency of the timestamp counter.
105    pub tsc_freq: Option<u64>,
106    /// The epoch at which the guest started, if it has started.
107    /// This is used to calculate the time spent in the guest relative to the
108    /// time when the host started.
109    pub guest_start_epoch: Option<std::time::Instant>,
110    /// The start guest time, in TSC cycles, for the current guest has a double purpose.
111    /// This field is used in two ways:
112    /// 1. It contains the TSC value recorded on the host when the guest started.
113    ///    This is used to calculate the TSC frequency which is the same on the host and guest.
114    ///    The TSC frequency is used to convert TSC values to timestamps in the trace.
115    ///    **NOTE**: This is only used until the TSC frequency is calculated, when the first
116    ///    records are received.
117    /// 2. To store the TSC value at recorded on the guest when the guest started (first record
118    ///    received)
119    ///    This is used to calculate the records timestamps relative to when guest started.
120    pub guest_start_tsc: Option<u64>,
121    /// The file to which the trace is being written
122    #[allow(dead_code)]
123    pub file: Arc<Mutex<std::fs::File>>,
124    /// The unwind information for the current guest
125    #[cfg(feature = "unwind_guest")]
126    #[allow(dead_code)]
127    pub unwind_module: Arc<dyn crate::mem::exe::UnwindInfo>,
128    /// The framehop unwinder for the current guest
129    #[cfg(feature = "unwind_guest")]
130    pub unwinder: framehop::x86_64::UnwinderX86_64<Vec<u8>>,
131    /// The framehop cache
132    #[cfg(feature = "unwind_guest")]
133    pub unwind_cache: Arc<Mutex<framehop::x86_64::CacheX86_64>>,
134}
135#[cfg(feature = "trace_guest")]
136impl TraceInfo {
137    /// Create a new TraceInfo by saving the current time as the epoch
138    /// and generating a random filename.
139    pub fn new(
140        #[cfg(feature = "unwind_guest")] unwind_module: Arc<dyn crate::mem::exe::UnwindInfo>,
141    ) -> crate::Result<Self> {
142        let mut path = std::env::current_dir()?;
143        path.push("trace");
144
145        // create directory if it does not exist
146        if !path.exists() {
147            std::fs::create_dir(&path)?;
148        }
149        path.push(uuid::Uuid::new_v4().to_string());
150        path.set_extension("trace");
151
152        log::info!("Creating trace file at: {}", path.display());
153        println!("Creating trace file at: {}", path.display());
154
155        #[cfg(feature = "unwind_guest")]
156        let hash = unwind_module.hash();
157        #[cfg(feature = "unwind_guest")]
158        let (unwinder, unwind_cache) = {
159            let mut unwinder = framehop::x86_64::UnwinderX86_64::new();
160            unwinder.add_module(unwind_module.clone().as_module());
161            let cache = framehop::x86_64::CacheX86_64::new();
162            (unwinder, Arc::new(Mutex::new(cache)))
163        };
164        if !hyperlight_guest_tracing::invariant_tsc::has_invariant_tsc() {
165            // If the platform does not support invariant TSC, warn the user.
166            // On Azure nested virtualization, the TSC invariant bit is not correctly reported, this is a known issue.
167            log::warn!(
168                "Invariant TSC is not supported on this platform, trace timestamps may be inaccurate"
169            );
170        }
171
172        let ret = Self {
173            epoch: std::time::Instant::now(),
174            tsc_freq: None,
175            guest_start_epoch: None,
176            guest_start_tsc: None,
177            file: Arc::new(Mutex::new(std::fs::File::create_new(path)?)),
178            #[cfg(feature = "unwind_guest")]
179            unwind_module,
180            #[cfg(feature = "unwind_guest")]
181            unwinder,
182            #[cfg(feature = "unwind_guest")]
183            unwind_cache,
184        };
185        /* write a frame identifying the binary */
186        #[cfg(feature = "unwind_guest")]
187        self::outb::record_trace_frame(&ret, 0, |f| {
188            let _ = f.write_all(hash.as_bytes());
189        })?;
190        Ok(ret)
191    }
192
193    /// Calculate the TSC frequency based on the RDTSC instruction on the host.
194    fn calculate_tsc_freq(&mut self) -> crate::Result<()> {
195        let (start, start_time) = match (
196            self.guest_start_tsc.as_ref(),
197            self.guest_start_epoch.as_ref(),
198        ) {
199            (Some(start), Some(start_time)) => (*start, *start_time),
200            _ => {
201                // If the guest start TSC and time are not set, we use the current time and TSC.
202                // This is not ideal, but it allows us to calculate the TSC frequency without
203                // failing.
204                // This is a fallback mechanism to ensure that we can still calculate, however it
205                // should be noted that this may lead to inaccuracies in the TSC frequency.
206                // The start time should be already set before running the guest for each sandbox.
207                log::error!(
208                    "Guest start TSC and time are not set. Calculating TSC frequency will use current time and TSC."
209                );
210                (
211                    hyperlight_guest_tracing::invariant_tsc::read_tsc(),
212                    std::time::Instant::now(),
213                )
214            }
215        };
216
217        let end_time = std::time::Instant::now();
218        let end = hyperlight_guest_tracing::invariant_tsc::read_tsc();
219
220        let elapsed = end_time.duration_since(start_time).as_secs_f64();
221        let tsc_freq = ((end - start) as f64 / elapsed) as u64;
222
223        log::info!("Calculated TSC frequency: {} Hz", tsc_freq);
224        self.tsc_freq = Some(tsc_freq);
225
226        Ok(())
227    }
228}
229
230pub(crate) trait WrapperGetter {
231    #[allow(dead_code)]
232    fn get_mgr_wrapper(&self) -> &MemMgrWrapper<HostSharedMemory>;
233    fn get_mgr_wrapper_mut(&mut self) -> &mut MemMgrWrapper<HostSharedMemory>;
234}
235
236#[cfg(test)]
237mod tests {
238    use std::sync::Arc;
239    use std::thread;
240
241    use crossbeam_queue::ArrayQueue;
242    use hyperlight_testing::simple_guest_as_string;
243
244    use crate::sandbox::uninitialized::GuestBinary;
245    use crate::{MultiUseSandbox, UninitializedSandbox, new_error};
246
247    #[test]
248    // TODO: add support for testing on WHP
249    #[cfg(target_os = "linux")]
250    fn is_hypervisor_present() {
251        use std::path::Path;
252
253        cfg_if::cfg_if! {
254            if #[cfg(all(kvm, mshv))] {
255                assert_eq!(Path::new("/dev/kvm").exists() || Path::new("/dev/mshv").exists(), super::is_hypervisor_present());
256            } else if #[cfg(kvm)] {
257                assert_eq!(Path::new("/dev/kvm").exists(), super::is_hypervisor_present());
258            } else if #[cfg(mshv)] {
259                assert_eq!(Path::new("/dev/mshv").exists(), super::is_hypervisor_present());
260            } else {
261                assert!(!super::is_hypervisor_present());
262            }
263        }
264    }
265
266    #[test]
267    fn check_create_and_use_sandbox_on_different_threads() {
268        let unintializedsandbox_queue = Arc::new(ArrayQueue::<UninitializedSandbox>::new(10));
269        let sandbox_queue = Arc::new(ArrayQueue::<MultiUseSandbox>::new(10));
270
271        for i in 0..10 {
272            let simple_guest_path = simple_guest_as_string().expect("Guest Binary Missing");
273            let unintializedsandbox =
274                UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_path), None)
275                    .unwrap_or_else(|_| panic!("Failed to create UninitializedSandbox {}", i));
276
277            unintializedsandbox_queue
278                .push(unintializedsandbox)
279                .unwrap_or_else(|_| panic!("Failed to push UninitializedSandbox {}", i));
280        }
281
282        let thread_handles = (0..10)
283            .map(|i| {
284                let uq = unintializedsandbox_queue.clone();
285                let sq = sandbox_queue.clone();
286                thread::spawn(move || {
287                    let uninitialized_sandbox = uq.pop().unwrap_or_else(|| {
288                        panic!("Failed to pop UninitializedSandbox thread {}", i)
289                    });
290                    let host_funcs = uninitialized_sandbox
291                        .host_funcs
292                        .try_lock()
293                        .map_err(|_| new_error!("Error locking"));
294
295                    assert!(host_funcs.is_ok());
296
297                    host_funcs
298                        .unwrap()
299                        .host_print(format!(
300                            "Printing from UninitializedSandbox on Thread {}\n",
301                            i
302                        ))
303                        .unwrap();
304
305                    let sandbox = uninitialized_sandbox.evolve().unwrap_or_else(|_| {
306                        panic!("Failed to initialize UninitializedSandbox thread {}", i)
307                    });
308
309                    sq.push(sandbox).unwrap_or_else(|_| {
310                        panic!("Failed to push UninitializedSandbox thread {}", i)
311                    })
312                })
313            })
314            .collect::<Vec<_>>();
315
316        for handle in thread_handles {
317            handle.join().unwrap();
318        }
319
320        let thread_handles = (0..10)
321            .map(|i| {
322                let sq = sandbox_queue.clone();
323                thread::spawn(move || {
324                    let sandbox = sq
325                        .pop()
326                        .unwrap_or_else(|| panic!("Failed to pop Sandbox thread {}", i));
327                    let host_funcs = sandbox
328                        ._host_funcs
329                        .try_lock()
330                        .map_err(|_| new_error!("Error locking"));
331
332                    assert!(host_funcs.is_ok());
333
334                    host_funcs
335                        .unwrap()
336                        .host_print(format!("Print from Sandbox on Thread {}\n", i))
337                        .unwrap();
338                })
339            })
340            .collect::<Vec<_>>();
341
342        for handle in thread_handles {
343            handle.join().unwrap();
344        }
345    }
346}