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