Skip to main content

MultiUseSandbox

Struct MultiUseSandbox 

Source
pub struct MultiUseSandbox { /* private fields */ }
Expand description

A fully initialized sandbox that can execute guest function calls.

Guest functions can be called repeatedly while maintaining state between calls. The sandbox supports creating snapshots and restoring to previous states.

§Sandbox status

The sandbox becomes Poisoned when guest execution does not complete normally. Causes include guest panics or aborts, invalid memory access, stack overflow, heap exhaustion, and cancellation through InterruptHandle::kill(). Interrupted execution can leak allocations, corrupt allocator metadata, leave resources locked, or partially update state.

Use restore() with a snapshot taken before the interrupted execution to make a poisoned sandbox ready again. Restore reinstates the captured memory and vCPU state and removes dynamic mappings.

A restore failure that prevents Hyperlight from establishing valid base memory mappings can leave the sandbox Unrecoverable. Further restore attempts and guest operations are rejected. The sandbox must be discarded.

Implementations§

Source§

impl MultiUseSandbox

Source

pub fn set_pt_root_finder(&mut self, finder: PtRootFinder)

Set a callback that discovers page table roots from guest memory. The callback receives (snapshot_mem, scratch_mem, cr3) and returns the list of root GPAs to walk during snapshot creation.

The callback must support every guest restored into this sandbox.

Source

pub fn from_snapshot( snapshot: Arc<Snapshot>, host_funcs: HostFunctions, config: Option<SandboxConfiguration>, ) -> Result<Self>

Create a MultiUseSandbox directly from a Snapshot, bypassing guest binary loading and initialization.

This is useful for fast sandbox creation when a snapshot of an already-initialized guest is available, either saved to disk or captured in memory from another sandbox.

The provided [HostFunctions] must include every host function that was registered on the sandbox at the time the snapshot was taken (matched by name and signature). Additional host functions not present in the snapshot are allowed. A mismatch returns SnapshotHostFunctionMismatch carrying the missing names and signature differences.

An optional SandboxConfiguration can be supplied to override runtime settings such as timeouts and interrupt behavior. Memory layout fields (input_data_size, output_data_size, heap_size, scratch_size) are always taken from the snapshot. Any values supplied in config for those fields are ignored. On x86_64 the config must declare every guest MSR the snapshot was taken with (see SandboxConfiguration::guest_msrs), or the load fails with an MSR mismatch.

§Examples

From a snapshot taken on another sandbox:

// Create and initialize a sandbox the normal way
let mut sandbox = SandboxBuilder::from_file("guest.bin").build()?;

// Capture a snapshot of the initialized state
let snapshot = sandbox.snapshot()?;

// Create a new sandbox directly from the snapshot
let mut sandbox2 = MultiUseSandbox::from_snapshot(snapshot, HostFunctions::default(), None)?;
let result: i32 = sandbox2.call("GetValue", ())?;

From a snapshot loaded from disk:

let tag = OciTag::new("latest")?;
let snapshot = Arc::new(Snapshot::load("./guest_snapshot", tag)?);
let mut sandbox = MultiUseSandbox::from_snapshot(snapshot, HostFunctions::default(), None)?;
let result: String = sandbox.call("Echo", "hello".to_string())?;
Source

pub fn snapshot(&mut self) -> Result<Arc<Snapshot>>

Creates a snapshot of the sandbox’s current memory state.

The returned snapshot can be applied to any MultiUseSandbox whose registered host functions are a superset of those registered here at the time of capture. See MultiUseSandbox::restore and MultiUseSandbox::from_snapshot for the exact compatibility rules and the error variants returned on mismatch.

On x86_64, the snapshot saves a small core of essential CPU state plus each MSR declared with SandboxBuilder::guest_msrs.

§Sandbox status

This method returns crate::HyperlightError::PoisonedSandbox when the sandbox is poisoned and crate::HyperlightError::UnrecoverableSandbox when it is unrecoverable.

§Examples
let mut sandbox = SandboxBuilder::from_file("guest.bin").build()?;

// Modify sandbox state
sandbox.call_guest_function_by_name::<i32>("SetValue", 42)?;

// Capture a snapshot of the current memory state
let snapshot = sandbox.snapshot()?;
Source

pub fn restore(&mut self, snapshot: Arc<Snapshot>) -> Result<()>

Restores the sandbox’s memory to a previously captured snapshot state.

The sandbox’s registered host functions must be a superset of those required by the snapshot (matched by name and signature). Extras on the sandbox are allowed. The registry itself is left unchanged. A mismatch returns SnapshotHostFunctionMismatch carrying the missing names and signature differences.

On x86_64, this restores the MSR state captured by MultiUseSandbox::snapshot: SandboxBuilder::guest_msrs selects which MSRs are saved and restored.

Restore writes the snapshot’s saved MSRs. On KVM the destination must declare every MSR the snapshot saved. An MSR restore failure leaves the sandbox poisoned.

§Status after restore

A successful restore sets the status to Ready. The restored state includes snapshot and scratch memory, vCPU state, stack state, the next VM action, captured MSRs on x86_64, and the removal of dynamic memory mappings. This discards leaked allocations, restores allocator and lock state, and rolls back partial updates.

Restore failures have three status outcomes:

  • Snapshot compatibility failures happen before mutation and leave the current status unchanged.
  • A failure while restoring base memory or its VM mappings sets the status to Unrecoverable. The sandbox must be discarded.
  • A later failure while restoring vCPU state, MSRs, or dynamic mappings leaves the sandbox Poisoned. Restore can be retried with a compatible snapshot.

Calling this method on an unrecoverable sandbox returns crate::HyperlightError::UnrecoverableSandbox.

§Examples
let mut sandbox = SandboxBuilder::from_file("guest.bin").build()?;

// Take initial snapshot from this sandbox
let snapshot = sandbox.snapshot()?;

// Modify sandbox state
sandbox.call_guest_function_by_name::<i32>("SetValue", 100)?;
let value: i32 = sandbox.call_guest_function_by_name("GetValue", ())?;
assert_eq!(value, 100);

// Restore to previous state (same sandbox)
sandbox.restore(snapshot)?;
let restored_value: i32 = sandbox.call_guest_function_by_name("GetValue", ())?;
assert_eq!(restored_value, 0); // Back to initial state
§Recovering from Poison
let mut sandbox = SandboxBuilder::from_file("guest.bin").build()?;

// Take snapshot before potentially poisoning operation
let snapshot = sandbox.snapshot()?;

// This might poison the sandbox (guest not run to completion)
let result = sandbox.call::<()>("guest_panic", ());
if result.is_err() {
    if sandbox.status().is_poisoned() {
        // Restore from snapshot to clear poison
        sandbox.restore(snapshot.clone())?;
        assert!(sandbox.status().is_ready());
         
        // Sandbox is now usable again
        sandbox.call::<String>("Echo", "hello".to_string())?;
    }
}
Source

pub fn call<Output: SupportedReturnType>( &mut self, func_name: &str, args: impl ParameterTuple, ) -> Result<Output>

Calls a guest function by name with the specified arguments.

Changes made to the sandbox during execution are persisted.

§Poisoned Sandbox

This method will return crate::HyperlightError::PoisonedSandbox if the sandbox is already poisoned before the call. Use restore() to recover from a poisoned state.

§Sandbox Poisoning

If this method returns an error, the sandbox may be poisoned if the guest was not run to completion (due to panic, abort, memory violation, stack/heap exhaustion, or forced termination). Use status() to check the sandbox state and restore() to recover if needed.

If this method returns Ok, the sandbox is guaranteed to not be poisoned - the guest function completed successfully and the sandbox state is consistent.

§Examples
let mut sandbox = SandboxBuilder::from_file("guest.bin").build()?;

// Call function with no arguments
let result: i32 = sandbox.call("GetCounter", ())?;

// Call function with single argument
let doubled: i32 = sandbox.call("Double", 21)?;
assert_eq!(doubled, 42);

// Call function with multiple arguments
let sum: i32 = sandbox.call("Add", (10, 32))?;
assert_eq!(sum, 42);

// Call function returning string
let message: String = sandbox.call("Echo", "Hello, World!".to_string())?;
assert_eq!(message, "Hello, World!");
§Handling Potential Poisoning
let mut sandbox = SandboxBuilder::from_file("guest.bin").build()?;

// Take snapshot before risky operation
let snapshot = sandbox.snapshot()?;

// Call potentially unsafe guest function
let result = sandbox.call::<String>("RiskyOperation", "input".to_string());

// Check if the call failed and poisoned the sandbox
if let Err(e) = result {
    eprintln!("Guest function failed: {}", e);
     
    if sandbox.status().is_poisoned() {
        eprintln!("Sandbox was poisoned, restoring from snapshot");
        sandbox.restore(snapshot.clone())?;
    }
}
Source

pub unsafe fn map_region(&mut self, rgn: &MemoryRegion) -> Result<()>

Maps a region of host memory into the sandbox address space.

The base address and length must meet platform alignment requirements (typically page-aligned). The region_type field is ignored as guest page table entries are not created.

§Poisoned Sandbox

This method will return crate::HyperlightError::PoisonedSandbox if the sandbox is currently poisoned. Use restore() to recover from a poisoned state.

§Safety

The caller must ensure the host memory region remains valid and unmodified for the lifetime of self.

Source

pub fn map_file_cow(&mut self, file_path: &Path, guest_base: u64) -> Result<u64>

Map the contents of a file into the guest at a particular address

Returns the length of the mapping in bytes.

§Poisoned Sandbox

This method will return crate::HyperlightError::PoisonedSandbox if the sandbox is currently poisoned. Use restore() to recover from a poisoned state.

Source

pub fn interrupt_handle(&self) -> Arc<dyn InterruptHandle>

Returns a handle for interrupting guest execution.

§Examples
let mut sandbox = SandboxBuilder::from_file("guest.bin").build()?;

// Get interrupt handle before starting long-running operation
let interrupt_handle = sandbox.interrupt_handle();

// Spawn thread to interrupt after timeout
let handle_clone = interrupt_handle.clone();
thread::spawn(move || {
    thread::sleep(Duration::from_secs(5));
    handle_clone.kill();
});

// This call may be interrupted by the spawned thread
let result = sandbox.call_guest_function_by_name::<i32>("LongRunningFunction", ());
Source

pub fn poisoned(&self) -> bool

👎Deprecated since 0.17.0:

use status().is_poisoned()

Returns whether the sandbox is poisoned.

Use status() to distinguish every lifecycle state.

§Causes of Poisoning

The sandbox becomes poisoned when guest execution is interrupted:

  • Panics/Aborts - Guest code panics or calls abort()
  • Invalid Memory Access - Read/write/execute violations
  • Stack Overflow - Guest exhausts stack space
  • Heap Exhaustion - Guest runs out of heap memory
  • Forced Termination - InterruptHandle::kill() called during execution
§Recovery

To clear the poison state, use restore() with a snapshot that was taken before the sandbox became poisoned.

§Examples
let mut sandbox = SandboxBuilder::from_file("guest.bin").build()?;

if sandbox.status().is_poisoned() {
    println!("Sandbox is poisoned");
}
Source

pub fn status(&self) -> SandboxStatus

Returns the sandbox lifecycle status.

  • Ready permits guest operations and snapshots.
  • Poisoned rejects guest operations and snapshots. A successful restore() makes it ready.
  • Unrecoverable rejects all further operations, including restore. The sandbox must be discarded.

Trait Implementations§

Source§

impl Callable for MultiUseSandbox

Source§

fn call<Output: SupportedReturnType>( &mut self, func_name: &str, args: impl ParameterTuple, ) -> Result<Output>

Call a guest function dynamically
Source§

impl Debug for MultiUseSandbox

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Registerable for MultiUseSandbox

Allow registering host functions on an already-evolved crate::MultiUseSandbox.

The primary entry point for host-function registration is crate::SandboxBuilder::host_function — that’s the lifecycle phase where the guest hasn’t yet been allowed to issue host calls. There are, however, cases where a MultiUseSandbox is obtained without going through the builder:

  • Sandboxes loaded from a persisted snapshot.
  • Any future API that yields a MultiUseSandbox directly.

In those cases the caller never had a chance to register up front, so we expose the same trait implementation here for late registration. The guest’s host-function dispatcher resolves by name at call time, so inserting into the registry after the sandbox is built is semantically safe as long as the first host-function invocation happens after registration completes.

Source§

fn register_host_function<Args: ParameterTuple, Output: SupportedReturnType>( &mut self, name: &str, hf: impl Into<HostFunction<Output, Args>>, ) -> Result<()>

Register a primitive host function

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more