Skip to content

Add a sandbox builder #763

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/hyperlight_host/src/func/host_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub trait Registerable {
eas: Vec<ExtraAllowedSyscall>,
) -> Result<()>;
}

impl Registerable for UninitializedSandbox {
fn register_host_function<Args: ParameterTuple, Output: SupportedReturnType>(
&mut self,
Expand Down Expand Up @@ -121,8 +122,9 @@ where
func: Arc<dyn Fn(Args) -> Result<Output> + Send + Sync + 'static>,
}

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

impl<Args, Output> HostFunction<Output, Args>
Expand All @@ -149,7 +151,7 @@ where
{
fn from(func: HostFunction<Output, Args>) -> TypeErasedHostFunction {
TypeErasedHostFunction {
func: Box::new(move |args: Vec<ParameterValue>| {
func: Arc::new(move |args: Vec<ParameterValue>| {
let args = Args::from_value(args)?;
Ok(func.call(args)?.into_value())
}),
Expand Down
1 change: 1 addition & 0 deletions src/hyperlight_host/src/sandbox/host_funcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ impl From<&mut FunctionRegistry> for HostFunctionDetails {
}
}

#[derive(Clone)]
pub struct FunctionEntry {
pub function: TypeErasedHostFunction,
pub extra_allowed_syscalls: Option<Vec<ExtraAllowedSyscall>>,
Expand Down
7 changes: 7 additions & 0 deletions src/hyperlight_host/src/sandbox/initialized_multi_use.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use tracing::{Span, instrument};

use super::host_funcs::FunctionRegistry;
use super::snapshot::Snapshot;
use super::uninitialized::Builder;
use super::{Callable, MemMgrWrapper, WrapperGetter};
use crate::HyperlightError::SnapshotSandboxMismatch;
use crate::func::guest_err::check_for_guest_error;
Expand Down Expand Up @@ -66,6 +67,12 @@ pub struct MultiUseSandbox {
}

impl MultiUseSandbox {
/// A builder for `Sandbox`.
/// This builder allows you to configure the sandbox, and register host functions.
pub fn builder() -> Builder {
Builder::default()
}

/// Move an `UninitializedSandbox` into a new `MultiUseSandbox` instance.
///
/// This function is not equivalent to doing an `evolve` from uninitialized
Expand Down
215 changes: 214 additions & 1 deletion src/hyperlight_host/src/sandbox/uninitialized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

use std::collections::HashMap;
use std::fmt::Debug;
use std::option::Option;
use std::path::Path;
use std::sync::{Arc, Mutex};
#[cfg(target_os = "linux")]
use std::time::Duration;

use log::LevelFilter;
use tracing::{Span, instrument};

use super::host_funcs::{FunctionRegistry, default_writer_func};
use super::host_funcs::{FunctionEntry, FunctionRegistry, default_writer_func};
use super::mem_mgr::MemMgrWrapper;
use super::uninitialized_evolve::evolve_impl_multi_use;
use crate::func::host_functions::{HostFunction, register_host_function};
Expand All @@ -34,6 +37,8 @@ use crate::mem::memory_region::{DEFAULT_GUEST_BLOB_MEM_FLAGS, MemoryRegionFlags}
use crate::mem::mgr::{STACK_COOKIE_LEN, SandboxMemoryManager};
use crate::mem::shared_mem::ExclusiveSharedMemory;
use crate::sandbox::SandboxConfiguration;
#[cfg(gdb)]
use crate::sandbox::config::DebugInfo;
use crate::{MultiUseSandbox, Result, new_error};

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

/// A builder for `Sandbox`.
/// This builder allows you to configure the sandbox, and register host functions.
#[derive(Default)]
pub struct Builder {
config: SandboxConfiguration,
host_functions: HashMap<String, FunctionEntry>,
}

impl Builder {
/// The default size of input data
pub const DEFAULT_INPUT_SIZE: usize = SandboxConfiguration::DEFAULT_INPUT_SIZE;
/// The minimum size of input data
pub const MIN_INPUT_SIZE: usize = SandboxConfiguration::MIN_INPUT_SIZE;
/// The default size of output data
pub const DEFAULT_OUTPUT_SIZE: usize = SandboxConfiguration::DEFAULT_OUTPUT_SIZE;
/// The minimum size of output data
pub const MIN_OUTPUT_SIZE: usize = SandboxConfiguration::MIN_OUTPUT_SIZE;
/// The default size of host function definitionsSET
/// Host function definitions has its own page in memory, in order to be READ-ONLY
/// from a guest's perspective.
pub const DEFAULT_HOST_FUNCTION_DEFINITION_SIZE: usize =
SandboxConfiguration::DEFAULT_HOST_FUNCTION_DEFINITION_SIZE;
/// The minimum size of host function definitions
pub const MIN_HOST_FUNCTION_DEFINITION_SIZE: usize =
SandboxConfiguration::MIN_HOST_FUNCTION_DEFINITION_SIZE;
/// The default interrupt retry delay
#[cfg(target_os = "linux")]
pub const DEFAULT_INTERRUPT_RETRY_DELAY: Duration =
SandboxConfiguration::DEFAULT_INTERRUPT_RETRY_DELAY;
/// The default signal offset from `SIGRTMIN` used to determine the signal number for interrupting
#[cfg(target_os = "linux")]
pub const INTERRUPT_VCPU_SIGRTMIN_OFFSET: u8 =
SandboxConfiguration::INTERRUPT_VCPU_SIGRTMIN_OFFSET;

/// Set the size of the memory buffer that is made available for serialising host function definitions
/// the minimum value is MIN_HOST_FUNCTION_DEFINITION_SIZE
pub fn host_function_definition_size(&mut self, bytes: usize) -> &mut Self {
self.config.set_host_function_definition_size(bytes);
self
}

/// Set the size of the memory buffer that is made available for input to the guest
/// the minimum value is MIN_INPUT_SIZE
pub fn input_data_size(&mut self, bytes: usize) -> &mut Self {
self.config.set_input_data_size(bytes);
self
}

/// Set the size of the memory buffer that is made available for output from the guest
/// the minimum value is MIN_OUTPUT_SIZE
pub fn output_data_size(&mut self, output_data_size: usize) -> &mut Self {
self.config.set_output_data_size(output_data_size);
self
}

/// Set the stack size to use in the guest sandbox.
/// If set to 0, the stack size will be determined from the PE file header
pub fn stack_size(&mut self, bytes: usize) -> &mut Self {
self.config.set_stack_size(bytes as u64);
self
}

/// Set the heap size to use in the guest sandbox.
/// If set to 0, the heap size will be determined from the PE file header
pub fn heap_size(&mut self, bytes: usize) -> &mut Self {
self.config.set_heap_size(bytes as u64);
self
}

/// Sets the interrupt retry delay
#[cfg(target_os = "linux")]
pub fn interrupt_retry_delay(&mut self, delay: Duration) -> &mut Self {
self.config.set_interrupt_retry_delay(delay);
self
}

/// Sets the offset from `SIGRTMIN` to determine the real-time signal used for
/// interrupting the VCPU thread.
///
/// The final signal number is computed as `SIGRTMIN + offset`, and it must fall within
/// the valid range of real-time signals supported by the host system.
///
/// Returns an error if the offset exceeds the maximum real-time signal number.
#[cfg(target_os = "linux")]
pub fn interrupt_vcpu_sigrtmin_offset(&mut self, offset: u8) -> Result<&mut Self> {
self.config.set_interrupt_vcpu_sigrtmin_offset(offset)?;
Ok(self)
}

/// Enables the guest core dump generation for a sandbox
#[cfg(crashdump)]
pub fn enable_core_dump(&mut self) -> &mut Self {
self.config.set_guest_core_dump(true);
self
}

/// Sets the configuration for the guest debug
#[cfg(gdb)]
pub fn debug_info(&mut self, debug_info: DebugInfo) -> &mut Self {
self.config.set_guest_debug_info(debug_info);
self
}

/// Register a host function with the given name in the sandbox.
pub fn register<Args: ParameterTuple, Output: SupportedReturnType>(
&mut self,
name: impl AsRef<str>,
host_func: impl Into<HostFunction<Output, Args>>,
) -> &mut Self {
let name = name.as_ref().to_string();
let entry = FunctionEntry {
function: host_func.into().into(),
extra_allowed_syscalls: None,
parameter_types: Args::TYPE,
return_type: Output::TYPE,
};
self.host_functions.insert(name, entry);
self
}

/// Register the host function with the given name in the sandbox.
/// Unlike `register`, this variant takes a list of extra syscalls that will
/// allowed during the execution of the function handler.
#[cfg(all(feature = "seccomp", target_os = "linux"))]
pub fn register_with_syscalls<Args: ParameterTuple, Output: SupportedReturnType>(
&mut self,
name: impl AsRef<str>,
host_func: impl Into<HostFunction<Output, Args>>,
extra_allowed_syscalls: impl IntoIterator<Item = crate::sandbox::ExtraAllowedSyscall>,
) -> &mut Self {
let name = name.as_ref().to_string();
let entry = FunctionEntry {
function: host_func.into().into(),
extra_allowed_syscalls: Some(extra_allowed_syscalls.into_iter().collect()),
parameter_types: Args::TYPE,
return_type: Output::TYPE,
};
self.host_functions.insert(name, entry);
self
}

/// Register a host function named "HostPrint" that will be called by the guest
/// when it wants to print to the console.
/// The "HostPrint" host function is kind of special, as we expect it to have the
/// `FnMut(String) -> i32` signature.
pub fn register_print(
&mut self,
print_func: impl Into<HostFunction<i32, (String,)>>,
) -> &mut Self {
#[cfg(not(all(target_os = "linux", feature = "seccomp")))]
self.register("HostPrint", print_func);

#[cfg(all(target_os = "linux", feature = "seccomp"))]
self.register_with_syscalls(
"HostPrint",
print_func,
EXTRA_ALLOWED_SYSCALLS_FOR_WRITER_FUNC.iter().copied(),
);

self
}

/// Register a host function named "HostPrint" that will be called by the guest
/// when it wants to print to the console.
/// The "HostPrint" host function is kind of special, as we expect it to have the
/// `FnMut(String) -> i32` signature.
/// Unlike `register_print`, this variant takes a list of extra syscalls that will
/// allowed during the execution of the function handler.
#[cfg(all(target_os = "linux", feature = "seccomp"))]
pub fn register_print_with_syscalls(
&mut self,
print_func: impl Into<HostFunction<i32, (String,)>>,
extra_allowed_syscalls: impl IntoIterator<Item = crate::sandbox::ExtraAllowedSyscall>,
) -> &mut Self {
self.register_with_syscalls(
"HostPrint",
print_func,
EXTRA_ALLOWED_SYSCALLS_FOR_WRITER_FUNC
.iter()
.copied()
.chain(extra_allowed_syscalls),
)
}

/// Build a new sandbox configured to run the binary specified by `env`.
pub fn build<'a, 'b>(
&mut self,
env: impl Into<GuestEnvironment<'a, 'b>>,
) -> Result<MultiUseSandbox> {
#![allow(deprecated)]
let mut sandbox = UninitializedSandbox::new(env, Some(self.config))?;
#[allow(clippy::unwrap_used)]
// unwrap is ok since no other thread will be holding the lock at this point
let mut host_functions = sandbox.host_funcs.try_lock().unwrap();
for (name, entry) in self.host_functions.iter() {
host_functions.register_host_function(
name.to_string(),
entry.clone(),
sandbox.mgr.unwrap_mgr_mut(),
)?;
}
drop(host_functions);
sandbox.evolve()
}
}

impl Debug for UninitializedSandbox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UninitializedSandbox")
Expand Down
Loading