Skip to main content

nub_host_kvm/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 tracing::{Span, instrument};
23use tracing_core::LevelFilter;
24
25use super::host_funcs::FunctionRegistry;
26use super::snapshot::Snapshot;
27use super::uninitialized_evolve::evolve_impl_multi_use;
28use crate::func::HostFn;
29use crate::func::host_functions::register_host_function;
30use crate::mem::memory_region::{DEFAULT_GUEST_BLOB_MEM_FLAGS, MemoryRegionFlags};
31use crate::mem::mgr::SandboxMemoryManager;
32use crate::mem::shared_mem::{ExclusiveSharedMemory, SharedMemory};
33use crate::sandbox::SandboxConfiguration;
34use crate::{MultiUseSandbox, Result, new_error};
35
36/// A preliminary sandbox that represents allocated memory and registered host functions,
37/// but has not yet created the underlying virtual machine.
38///
39/// This struct holds the configuration and setup needed for a sandbox without actually
40/// creating the VM. It allows you to:
41/// - Set up memory layout and load guest binary data
42/// - Register host functions that will be available to the guest
43/// - Configure sandbox settings before VM creation
44///
45/// The virtual machine is not created until you call [`evolve`](Self::evolve) to transform
46/// this into an initialized [`MultiUseSandbox`].
47pub struct UninitializedSandbox {
48    /// Registered host functions
49    pub(crate) host_funcs: Arc<Mutex<FunctionRegistry>>,
50    /// The memory manager for the sandbox.
51    pub(crate) mgr: SandboxMemoryManager<ExclusiveSharedMemory>,
52    pub(crate) max_guest_log_level: Option<LevelFilter>,
53    pub(crate) config: SandboxConfiguration,
54    pub(crate) load_info: crate::mem::exe::LoadInfo,
55    // This is needed to convey the stack pointer between the snapshot
56    // and the HyperlightVm creation
57    pub(crate) stack_top_gva: u64,
58}
59
60impl Debug for UninitializedSandbox {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        f.debug_struct("UninitializedSandbox")
63            .field("memory_layout", &self.mgr.layout)
64            .finish()
65    }
66}
67
68/// A `GuestBinary` is either a buffer or the file path to some data (e.g., a guest binary).
69#[derive(Debug)]
70pub enum GuestBinary<'a> {
71    /// A buffer containing the GuestBinary
72    Buffer(&'a [u8]),
73    /// A path to the GuestBinary
74    FilePath(String),
75}
76impl<'a> GuestBinary<'a> {
77    /// If the guest binary is identified by a file, canonicalise the path
78    ///
79    /// For [`GuestBinary::FilePath`], this resolves the path to its canonical
80    /// form. For [`GuestBinary::Buffer`], this method is a no-op.
81    /// TODO: Maybe we should make the GuestEnvironment or
82    ///       GuestBinary constructors crate-private and turn this
83    ///       into an invariant on one of those types.
84    pub fn canonicalize(&mut self) -> Result<()> {
85        if let GuestBinary::FilePath(p) = self {
86            let canon = Path::new(&p)
87                .canonicalize()
88                .map_err(|e| new_error!("GuestBinary not found: '{}': {}", p, e))?
89                .into_os_string()
90                .into_string()
91                .map_err(|e| new_error!("Error converting OsString to String: {:?}", e))?;
92            *self = GuestBinary::FilePath(canon)
93        }
94        Ok(())
95    }
96}
97
98/// A `GuestBlob` containing data and the permissions for its use.
99#[derive(Debug)]
100pub struct GuestBlob<'a> {
101    /// The data contained in the blob.
102    pub data: &'a [u8],
103    /// The permissions for the blob in memory.
104    /// By default, it's READ
105    pub permissions: MemoryRegionFlags,
106}
107
108impl<'a> From<&'a [u8]> for GuestBlob<'a> {
109    fn from(data: &'a [u8]) -> Self {
110        GuestBlob {
111            data,
112            permissions: DEFAULT_GUEST_BLOB_MEM_FLAGS,
113        }
114    }
115}
116
117/// Container for a guest binary and optional initialization data.
118///
119/// This struct combines a guest binary (either from a file or memory buffer) with
120/// optional data that will be available to the guest during execution.
121#[derive(Debug)]
122pub struct GuestEnvironment<'a, 'b> {
123    /// The guest binary, which can be a file path or a buffer.
124    pub guest_binary: GuestBinary<'a>,
125    /// An optional guest blob, which can be used to provide additional data to the guest.
126    pub init_data: Option<GuestBlob<'b>>,
127}
128
129impl<'a, 'b> GuestEnvironment<'a, 'b> {
130    /// Creates a new `GuestEnvironment` with the given guest binary and an optional guest blob.
131    pub fn new(guest_binary: GuestBinary<'a>, init_data: Option<&'b [u8]>) -> Self {
132        GuestEnvironment {
133            guest_binary,
134            init_data: init_data.map(GuestBlob::from),
135        }
136    }
137}
138
139impl<'a> From<GuestBinary<'a>> for GuestEnvironment<'a, '_> {
140    fn from(guest_binary: GuestBinary<'a>) -> Self {
141        GuestEnvironment {
142            guest_binary,
143            init_data: None,
144        }
145    }
146}
147
148impl UninitializedSandbox {
149    // Creates a new uninitialized sandbox from a pre-built snapshot.
150    // Note that since memory configuration is part of the snapshot the only configuration
151    // that can be changed (from the original snapshot) is that defines the behaviour of
152    // `InterruptHandle` on Linux.
153    //
154    // This is ok for now as this is not a public function
155    fn from_snapshot(snapshot: Arc<Snapshot>, cfg: Option<SandboxConfiguration>) -> Result<Self> {
156        let sandbox_cfg = cfg.unwrap_or_default();
157
158        let mem_mgr_wrapper =
159            SandboxMemoryManager::<ExclusiveSharedMemory>::from_snapshot(snapshot.as_ref())?;
160
161        let host_funcs = Arc::new(Mutex::new(FunctionRegistry::default()));
162
163        let sandbox = Self {
164            host_funcs,
165            mgr: mem_mgr_wrapper,
166            max_guest_log_level: None,
167            config: sandbox_cfg,
168            load_info: snapshot.load_info(),
169            stack_top_gva: snapshot.stack_top_gva(),
170        };
171
172        // Upstream registered a default "HostPrint" handler here.
173        // After the FB/SCALE → rkyv migration, host functions are
174        // fn_id-indexed and there is no host-print integration; if a
175        // future caller needs guest stdout it can register a handler
176        // explicitly via `register_host_function`.
177
178        crate::debug!("Sandbox created:  {:#?}", sandbox);
179
180        Ok(sandbox)
181    }
182
183    /// Creates a new uninitialized sandbox for the given guest environment.
184    ///
185    /// The guest binary can be provided as either a file path or memory buffer.
186    /// An optional configuration can customize memory sizes and sandbox settings.
187    /// After creation, register host functions using [`register`](Self::register)
188    /// before calling [`evolve`](Self::evolve) to complete initialization and create the VM.
189    #[instrument(
190        err(Debug),
191        skip(env),
192        parent = Span::current()
193    )]
194    pub fn new<'a, 'b>(
195        env: impl Into<GuestEnvironment<'a, 'b>>,
196        cfg: Option<SandboxConfiguration>,
197    ) -> Result<Self> {
198        let cfg = cfg.unwrap_or_default();
199        let env = env.into();
200        let snapshot = Snapshot::from_env(env, cfg)?;
201        Self::from_snapshot(Arc::new(snapshot), Some(cfg))
202    }
203
204    /// Creates and initializes the virtual machine, transforming this into a ready-to-use sandbox.
205    ///
206    /// This method consumes the `UninitializedSandbox` and performs the final initialization
207    /// steps to create the underlying virtual machine. Once evolved, the resulting
208    /// [`MultiUseSandbox`] can execute guest code and handle function calls.
209    #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
210    pub fn evolve(self) -> Result<MultiUseSandbox> {
211        evolve_impl_multi_use(self)
212    }
213
214    /// Returns the total size of the sandbox shared memory region in bytes.
215    ///
216    /// This is useful for placing file mappings at guest physical addresses
217    /// that don't overlap the primary shared memory slot.
218    pub fn shared_mem_size(&self) -> usize {
219        self.mgr.shared_mem.mem_size()
220    }
221
222    /// Sets the maximum log level for guest code execution.
223    ///
224    /// If not set, the log level is determined by the `RUST_LOG` environment variable,
225    /// defaulting to `LevelFilter::Error` if unset.
226    pub fn set_max_guest_log_level(&mut self, log_level: LevelFilter) {
227        self.max_guest_log_level = Some(log_level);
228    }
229
230    /// Registers a host function under `fn_id` that the guest can
231    /// call via the `OutBAction::CallFunction` outb port. The
232    /// closure receives the raw `Request.payload` bytes from the
233    /// guest and returns the raw response payload bytes.
234    pub fn register(&mut self, fn_id: u32, host_func: HostFn) -> Result<()> {
235        register_host_function(self, fn_id, host_func)
236    }
237}