Skip to content

Commit b2e8dd8

Browse files
committed
Add a sandbox builder
Signed-off-by: Jorge Prendes <[email protected]>
1 parent 104f62a commit b2e8dd8

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;
@@ -70,6 +71,12 @@ pub struct MultiUseSandbox {
7071
}
7172

7273
impl MultiUseSandbox {
74+
/// A builder for `Sandbox`.
75+
/// This builder allows you to configure the sandbox, and register host functions.
76+
pub fn builder() -> Builder {
77+
Builder::default()
78+
}
79+
7380
/// Move an `UninitializedSandbox` into a new `MultiUseSandbox` instance.
7481
///
7582
/// 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"))]
@@ -69,6 +74,8 @@ pub(crate) struct SandboxRuntimeConfig {
6974
/// host-implemented functions you need to be available to the guest, then
7075
/// call `evolve` to transform your
7176
/// `UninitializedSandbox` into an initialized `Sandbox`.
77+
#[doc(hidden)]
78+
//TODO: deprecate this #[deprecated(since = "0.8.0", note = "Deprecated in favour of Builder")]
7279
pub struct UninitializedSandbox {
7380
/// Registered host functions
7481
pub(crate) host_funcs: Arc<Mutex<FunctionRegistry>>,
@@ -81,6 +88,212 @@ pub struct UninitializedSandbox {
8188
pub(crate) load_info: crate::mem::exe::LoadInfo,
8289
}
8390

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

0 commit comments

Comments
 (0)