Skip to content

Commit 5245b79

Browse files
committed
Add a sandbox builder
Signed-off-by: Jorge Prendes <jorge.prendes@gmail.com>
1 parent e842ed4 commit 5245b79

File tree

4 files changed

+226
-3
lines changed

4 files changed

+226
-3
lines changed

src/hyperlight_host/src/func/host_functions.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ pub trait Registerable {
4343
eas: Vec<ExtraAllowedSyscall>,
4444
) -> Result<()>;
4545
}
46+
4647
impl Registerable for UninitializedSandbox {
4748
fn register_host_function<Args: ParameterTuple, Output: SupportedReturnType>(
4849
&mut self,
@@ -121,8 +122,9 @@ where
121122
func: Arc<dyn Fn(Args) -> Result<Output> + Send + Sync + 'static>,
122123
}
123124

125+
#[derive(Clone)]
124126
pub(crate) struct TypeErasedHostFunction {
125-
func: Box<dyn Fn(Vec<ParameterValue>) -> Result<ReturnValue> + Send + Sync + 'static>,
127+
func: Arc<dyn Fn(Vec<ParameterValue>) -> Result<ReturnValue> + Send + Sync + 'static>,
126128
}
127129

128130
impl<Args, Output> HostFunction<Output, Args>
@@ -149,7 +151,7 @@ where
149151
{
150152
fn from(func: HostFunction<Output, Args>) -> TypeErasedHostFunction {
151153
TypeErasedHostFunction {
152-
func: Box::new(move |args: Vec<ParameterValue>| {
154+
func: Arc::new(move |args: Vec<ParameterValue>| {
153155
let args = Args::from_value(args)?;
154156
Ok(func.call(args)?.into_value())
155157
}),

src/hyperlight_host/src/sandbox/host_funcs.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ impl From<&mut FunctionRegistry> for HostFunctionDetails {
5656
}
5757
}
5858

59+
#[derive(Clone)]
5960
pub struct FunctionEntry {
6061
pub function: TypeErasedHostFunction,
6162
pub extra_allowed_syscalls: Option<Vec<ExtraAllowedSyscall>>,

src/hyperlight_host/src/sandbox/initialized_multi_use.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use tracing::{Span, instrument};
3131

3232
use super::host_funcs::FunctionRegistry;
3333
use super::snapshot::Snapshot;
34+
use super::uninitialized::Builder;
3435
use super::{Callable, MemMgrWrapper, WrapperGetter};
3536
use crate::HyperlightError::SnapshotSandboxMismatch;
3637
use crate::func::guest_err::check_for_guest_error;
@@ -66,6 +67,12 @@ pub struct MultiUseSandbox {
6667
}
6768

6869
impl MultiUseSandbox {
70+
/// A builder for `Sandbox`.
71+
/// This builder allows you to configure the sandbox, and register host functions.
72+
pub fn builder() -> Builder {
73+
Builder::default()
74+
}
75+
6976
/// Move an `UninitializedSandbox` into a new `MultiUseSandbox` instance.
7077
///
7178
/// This function is not equivalent to doing an `evolve` from uninitialized

src/hyperlight_host/src/sandbox/uninitialized.rs

Lines changed: 214 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
use std::collections::HashMap;
1718
use std::fmt::Debug;
1819
use std::option::Option;
1920
use std::path::Path;
2021
use std::sync::{Arc, Mutex};
22+
#[cfg(target_os = "linux")]
23+
use std::time::Duration;
2124

2225
use log::LevelFilter;
2326
use tracing::{Span, instrument};
2427

25-
use super::host_funcs::{FunctionRegistry, default_writer_func};
28+
use super::host_funcs::{FunctionEntry, FunctionRegistry, default_writer_func};
2629
use super::mem_mgr::MemMgrWrapper;
2730
use super::uninitialized_evolve::evolve_impl_multi_use;
2831
use crate::func::host_functions::{HostFunction, register_host_function};
@@ -34,6 +37,8 @@ use crate::mem::memory_region::{DEFAULT_GUEST_BLOB_MEM_FLAGS, MemoryRegionFlags}
3437
use crate::mem::mgr::{STACK_COOKIE_LEN, SandboxMemoryManager};
3538
use crate::mem::shared_mem::ExclusiveSharedMemory;
3639
use crate::sandbox::SandboxConfiguration;
40+
#[cfg(gdb)]
41+
use crate::sandbox::config::DebugInfo;
3742
use crate::{MultiUseSandbox, Result, new_error};
3843

3944
#[cfg(all(target_os = "linux", feature = "seccomp"))]
@@ -73,6 +78,8 @@ pub(crate) struct SandboxRuntimeConfig {
7378
///
7479
/// The virtual machine is not created until you call [`evolve`](Self::evolve) to transform
7580
/// this into an initialized [`MultiUseSandbox`].
81+
#[doc(hidden)]
82+
//TODO: deprecate this #[deprecated(since = "0.8.0", note = "Deprecated in favour of Builder")]
7683
pub struct UninitializedSandbox {
7784
/// Registered host functions
7885
pub(crate) host_funcs: Arc<Mutex<FunctionRegistry>>,
@@ -85,6 +92,212 @@ pub struct UninitializedSandbox {
8592
pub(crate) load_info: crate::mem::exe::LoadInfo,
8693
}
8794

95+
/// A builder for `Sandbox`.
96+
/// This builder allows you to configure the sandbox, and register host functions.
97+
#[derive(Default)]
98+
pub struct Builder {
99+
config: SandboxConfiguration,
100+
host_functions: HashMap<String, FunctionEntry>,
101+
}
102+
103+
impl Builder {
104+
/// The default size of input data
105+
pub const DEFAULT_INPUT_SIZE: usize = SandboxConfiguration::DEFAULT_INPUT_SIZE;
106+
/// The minimum size of input data
107+
pub const MIN_INPUT_SIZE: usize = SandboxConfiguration::MIN_INPUT_SIZE;
108+
/// The default size of output data
109+
pub const DEFAULT_OUTPUT_SIZE: usize = SandboxConfiguration::DEFAULT_OUTPUT_SIZE;
110+
/// The minimum size of output data
111+
pub const MIN_OUTPUT_SIZE: usize = SandboxConfiguration::MIN_OUTPUT_SIZE;
112+
/// The default size of host function definitionsSET
113+
/// Host function definitions has its own page in memory, in order to be READ-ONLY
114+
/// from a guest's perspective.
115+
pub const DEFAULT_HOST_FUNCTION_DEFINITION_SIZE: usize =
116+
SandboxConfiguration::DEFAULT_HOST_FUNCTION_DEFINITION_SIZE;
117+
/// The minimum size of host function definitions
118+
pub const MIN_HOST_FUNCTION_DEFINITION_SIZE: usize =
119+
SandboxConfiguration::MIN_HOST_FUNCTION_DEFINITION_SIZE;
120+
/// The default interrupt retry delay
121+
#[cfg(target_os = "linux")]
122+
pub const DEFAULT_INTERRUPT_RETRY_DELAY: Duration =
123+
SandboxConfiguration::DEFAULT_INTERRUPT_RETRY_DELAY;
124+
/// The default signal offset from `SIGRTMIN` used to determine the signal number for interrupting
125+
#[cfg(target_os = "linux")]
126+
pub const INTERRUPT_VCPU_SIGRTMIN_OFFSET: u8 =
127+
SandboxConfiguration::INTERRUPT_VCPU_SIGRTMIN_OFFSET;
128+
129+
/// Set the size of the memory buffer that is made available for serialising host function definitions
130+
/// the minimum value is MIN_HOST_FUNCTION_DEFINITION_SIZE
131+
pub fn host_function_definition_size(&mut self, bytes: usize) -> &mut Self {
132+
self.config.set_host_function_definition_size(bytes);
133+
self
134+
}
135+
136+
/// Set the size of the memory buffer that is made available for input to the guest
137+
/// the minimum value is MIN_INPUT_SIZE
138+
pub fn input_data_size(&mut self, bytes: usize) -> &mut Self {
139+
self.config.set_input_data_size(bytes);
140+
self
141+
}
142+
143+
/// Set the size of the memory buffer that is made available for output from the guest
144+
/// the minimum value is MIN_OUTPUT_SIZE
145+
pub fn output_data_size(&mut self, output_data_size: usize) -> &mut Self {
146+
self.config.set_output_data_size(output_data_size);
147+
self
148+
}
149+
150+
/// Set the stack size to use in the guest sandbox.
151+
/// If set to 0, the stack size will be determined from the PE file header
152+
pub fn stack_size(&mut self, bytes: usize) -> &mut Self {
153+
self.config.set_stack_size(bytes as u64);
154+
self
155+
}
156+
157+
/// Set the heap size to use in the guest sandbox.
158+
/// If set to 0, the heap size will be determined from the PE file header
159+
pub fn heap_size(&mut self, bytes: usize) -> &mut Self {
160+
self.config.set_heap_size(bytes as u64);
161+
self
162+
}
163+
164+
/// Sets the interrupt retry delay
165+
#[cfg(target_os = "linux")]
166+
pub fn interrupt_retry_delay(&mut self, delay: Duration) -> &mut Self {
167+
self.config.set_interrupt_retry_delay(delay);
168+
self
169+
}
170+
171+
/// Sets the offset from `SIGRTMIN` to determine the real-time signal used for
172+
/// interrupting the VCPU thread.
173+
///
174+
/// The final signal number is computed as `SIGRTMIN + offset`, and it must fall within
175+
/// the valid range of real-time signals supported by the host system.
176+
///
177+
/// Returns an error if the offset exceeds the maximum real-time signal number.
178+
#[cfg(target_os = "linux")]
179+
pub fn interrupt_vcpu_sigrtmin_offset(&mut self, offset: u8) -> Result<&mut Self> {
180+
self.config.set_interrupt_vcpu_sigrtmin_offset(offset)?;
181+
Ok(self)
182+
}
183+
184+
/// Enables the guest core dump generation for a sandbox
185+
#[cfg(crashdump)]
186+
pub fn enable_core_dump(&mut self) -> &mut Self {
187+
self.config.set_guest_core_dump(true);
188+
self
189+
}
190+
191+
/// Sets the configuration for the guest debug
192+
#[cfg(gdb)]
193+
pub fn debug_info(&mut self, debug_info: DebugInfo) -> &mut Self {
194+
self.config.set_guest_debug_info(debug_info);
195+
self
196+
}
197+
198+
/// Register a host function with the given name in the sandbox.
199+
pub fn register<Args: ParameterTuple, Output: SupportedReturnType>(
200+
&mut self,
201+
name: impl AsRef<str>,
202+
host_func: impl Into<HostFunction<Output, Args>>,
203+
) -> &mut Self {
204+
let name = name.as_ref().to_string();
205+
let entry = FunctionEntry {
206+
function: host_func.into().into(),
207+
extra_allowed_syscalls: None,
208+
parameter_types: Args::TYPE,
209+
return_type: Output::TYPE,
210+
};
211+
self.host_functions.insert(name, entry);
212+
self
213+
}
214+
215+
/// Register the host function with the given name in the sandbox.
216+
/// Unlike `register`, this variant takes a list of extra syscalls that will
217+
/// allowed during the execution of the function handler.
218+
#[cfg(all(feature = "seccomp", target_os = "linux"))]
219+
pub fn register_with_syscalls<Args: ParameterTuple, Output: SupportedReturnType>(
220+
&mut self,
221+
name: impl AsRef<str>,
222+
host_func: impl Into<HostFunction<Output, Args>>,
223+
extra_allowed_syscalls: impl IntoIterator<Item = crate::sandbox::ExtraAllowedSyscall>,
224+
) -> &mut Self {
225+
let name = name.as_ref().to_string();
226+
let entry = FunctionEntry {
227+
function: host_func.into().into(),
228+
extra_allowed_syscalls: Some(extra_allowed_syscalls.into_iter().collect()),
229+
parameter_types: Args::TYPE,
230+
return_type: Output::TYPE,
231+
};
232+
self.host_functions.insert(name, entry);
233+
self
234+
}
235+
236+
/// Register a host function named "HostPrint" that will be called by the guest
237+
/// when it wants to print to the console.
238+
/// The "HostPrint" host function is kind of special, as we expect it to have the
239+
/// `FnMut(String) -> i32` signature.
240+
pub fn register_print(
241+
&mut self,
242+
print_func: impl Into<HostFunction<i32, (String,)>>,
243+
) -> &mut Self {
244+
#[cfg(not(all(target_os = "linux", feature = "seccomp")))]
245+
self.register("HostPrint", print_func);
246+
247+
#[cfg(all(target_os = "linux", feature = "seccomp"))]
248+
self.register_with_syscalls(
249+
"HostPrint",
250+
print_func,
251+
EXTRA_ALLOWED_SYSCALLS_FOR_WRITER_FUNC.iter().copied(),
252+
);
253+
254+
self
255+
}
256+
257+
/// Register a host function named "HostPrint" that will be called by the guest
258+
/// when it wants to print to the console.
259+
/// The "HostPrint" host function is kind of special, as we expect it to have the
260+
/// `FnMut(String) -> i32` signature.
261+
/// Unlike `register_print`, this variant takes a list of extra syscalls that will
262+
/// allowed during the execution of the function handler.
263+
#[cfg(all(target_os = "linux", feature = "seccomp"))]
264+
pub fn register_print_with_syscalls(
265+
&mut self,
266+
print_func: impl Into<HostFunction<i32, (String,)>>,
267+
extra_allowed_syscalls: impl IntoIterator<Item = crate::sandbox::ExtraAllowedSyscall>,
268+
) -> &mut Self {
269+
self.register_with_syscalls(
270+
"HostPrint",
271+
print_func,
272+
EXTRA_ALLOWED_SYSCALLS_FOR_WRITER_FUNC
273+
.iter()
274+
.copied()
275+
.chain(extra_allowed_syscalls),
276+
)
277+
}
278+
279+
/// Build a new sandbox configured to run the binary specified by `env`.
280+
pub fn build<'a, 'b>(
281+
&mut self,
282+
env: impl Into<GuestEnvironment<'a, 'b>>,
283+
) -> Result<MultiUseSandbox> {
284+
#![allow(deprecated)]
285+
let mut sandbox = UninitializedSandbox::new(env, Some(self.config))?;
286+
#[allow(clippy::unwrap_used)]
287+
// unwrap is ok since no other thread will be holding the lock at this point
288+
let mut host_functions = sandbox.host_funcs.try_lock().unwrap();
289+
for (name, entry) in self.host_functions.iter() {
290+
host_functions.register_host_function(
291+
name.to_string(),
292+
entry.clone(),
293+
sandbox.mgr.unwrap_mgr_mut(),
294+
)?;
295+
}
296+
drop(host_functions);
297+
sandbox.evolve()
298+
}
299+
}
300+
88301
impl Debug for UninitializedSandbox {
89302
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90303
f.debug_struct("UninitializedSandbox")

0 commit comments

Comments
 (0)