Skip to main content

hyperlight_js/sandbox/
sandbox_builder.rs

1/*
2Copyright 2026  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#[cfg(target_os = "linux")]
17use std::time::Duration;
18
19use hyperlight_host::sandbox::SandboxConfiguration;
20use hyperlight_host::{is_hypervisor_present, GuestBinary, HyperlightError, Result};
21
22use super::proto_js_sandbox::ProtoJSSandbox;
23use crate::HostPrintFn;
24
25/// A builder for a ProtoJSSandbox
26pub struct SandboxBuilder {
27    config: SandboxConfiguration,
28    host_print_fn: Option<HostPrintFn>,
29}
30
31/// The minimum scratch size for the JS runtime sandbox.
32///
33/// The scratch region provides writable physical memory for:
34///   - I/O buffers (input + output data)
35///   - Page table copies (proportional to snapshot size — our ~13 MB guest
36///     binary + heap produce ~72 KiB of page tables)
37///   - Dynamically allocated pages (GDT/IDT, stack growth, Copy-on-Write
38///     resolution during QuickJS initialisation)
39///   - Exception stack and metadata (2 pages at the top)
40///
41/// Hyperlight's default scratch (288 KiB) is far too small for the JS
42/// runtime guest: after fixed overheads there are only ~44 free pages,
43/// which are exhausted during init.  1 MiB (0x10_0000) matches
44/// hyperlight's own "large guest" test configuration and gives
45/// comfortable headroom.
46const MIN_SCRATCH_SIZE: usize = 0x10_0000; // 1 MiB
47
48/// The minimum heap size is 4 MiB.  The QuickJS engine needs a
49/// reasonable amount of heap during initialisation for builtins,
50/// global objects, and the bytecode compiler.  This lives in the
51/// identity-mapped snapshot region (NOT scratch).
52const MIN_HEAP_SIZE: u64 = 4096 * 1024;
53
54impl SandboxBuilder {
55    /// Create a new SandboxBuilder
56    pub fn new() -> Self {
57        let mut config = SandboxConfiguration::default();
58        config.set_heap_size(MIN_HEAP_SIZE);
59        config.set_scratch_size(MIN_SCRATCH_SIZE);
60
61        Self {
62            config,
63            host_print_fn: None,
64        }
65    }
66
67    /// Set the host print function
68    pub fn with_host_print_fn(mut self, host_print_fn: HostPrintFn) -> Self {
69        self.host_print_fn = Some(host_print_fn);
70        self
71    }
72
73    /// Set the guest output buffer size
74    pub fn with_guest_output_buffer_size(mut self, guest_output_buffer_size: usize) -> Self {
75        self.config.set_output_data_size(guest_output_buffer_size);
76        self
77    }
78
79    /// Set the guest input buffer size
80    /// This is the size of the buffer that the guest can write to
81    /// to send data to the host
82    /// The host can read from this buffer
83    /// The guest can write to this buffer
84    pub fn with_guest_input_buffer_size(mut self, guest_input_buffer_size: usize) -> Self {
85        self.config.set_input_data_size(guest_input_buffer_size);
86        self
87    }
88
89    /// Set the guest scratch size in bytes.
90    /// The scratch region provides writable memory for the guest, including the
91    /// dynamically-sized stack. Increase this if your guest code needs deep
92    /// recursion or large local variables.
93    /// Values smaller than the default (288KiB) are ignored.
94    pub fn with_guest_scratch_size(mut self, guest_scratch_size: usize) -> Self {
95        if guest_scratch_size > MIN_SCRATCH_SIZE {
96            self.config.set_scratch_size(guest_scratch_size);
97        }
98        self
99    }
100
101    /// Set the guest heap size
102    /// This is the size of the heap that code executing in the guest can use.
103    /// If this value is too small then the guest will fail, usually with a malloc failed error
104    /// The default (and minimum) value for this is set to the value of the MIN_HEAP_SIZE const.
105    pub fn with_guest_heap_size(mut self, guest_heap_size: u64) -> Self {
106        if guest_heap_size > MIN_HEAP_SIZE {
107            self.config.set_heap_size(guest_heap_size);
108        }
109        self
110    }
111
112    /// Sets the offset from `SIGRTMIN` to determine the real-time signal used for
113    /// interrupting the VCPU thread.
114    ///
115    /// The final signal number is computed as `SIGRTMIN + offset`, and it must fall within
116    /// the valid range of real-time signals supported by the host system.
117    ///
118    /// Returns Ok(()) if the offset is valid, or an error if it exceeds the maximum real-time signal number.
119    #[cfg(target_os = "linux")]
120    pub fn set_interrupt_vcpu_sigrtmin_offset(&mut self, offset: u8) -> Result<()> {
121        self.config.set_interrupt_vcpu_sigrtmin_offset(offset)?;
122        Ok(())
123    }
124
125    /// Sets the interrupt retry delay
126    /// This controls the delay between sending signals to the VCPU thread to interrupt it.
127    #[cfg(target_os = "linux")]
128    pub fn with_interrupt_retry_delay(mut self, delay: Duration) -> Self {
129        self.config.set_interrupt_retry_delay(delay);
130        self
131    }
132
133    /// Get the current configuration
134    pub fn get_config(&self) -> &SandboxConfiguration {
135        &self.config
136    }
137
138    /// Enable or disable crashdump generation for the sandbox
139    /// When enabled, core dumps will be generated when the guest crashes
140    /// This requires the `crashdump` feature to be enabled
141    #[cfg(feature = "crashdump")]
142    pub fn with_crashdump_enabled(mut self, enabled: bool) -> Self {
143        self.config.set_guest_core_dump(enabled);
144        self
145    }
146
147    /// Enable debugging for the guest runtime
148    /// This will allow the guest runtime to be natively debugged using GDB or
149    /// other debugging tools
150    ///
151    /// # Example:
152    /// ```rust
153    /// use hyperlight_js::SandboxBuilder;
154    /// let sandbox = SandboxBuilder::new()
155    ///    .with_debugging_enabled(8080) // Enable debugging on port 8080
156    ///    .build()
157    ///    .expect("Failed to build sandbox");
158    /// ```
159    /// # Note:
160    /// This method is only available when the `gdb` feature is enabled
161    /// and the code is compiled in debug mode.
162    #[cfg(all(feature = "gdb", debug_assertions))]
163    pub fn with_debugging_enabled(mut self, port: u16) -> Self {
164        let debug_info = hyperlight_host::sandbox::config::DebugInfo { port };
165        self.config.set_guest_debug_info(debug_info);
166        self
167    }
168
169    /// Build the ProtoJSSandbox
170    pub fn build(self) -> Result<ProtoJSSandbox> {
171        if !is_hypervisor_present() {
172            return Err(HyperlightError::NoHypervisorFound());
173        }
174        let guest_binary = GuestBinary::Buffer(super::JSRUNTIME);
175        let proto_js_sandbox =
176            ProtoJSSandbox::new(guest_binary, Some(self.config), self.host_print_fn)?;
177        Ok(proto_js_sandbox)
178    }
179}
180
181impl Default for SandboxBuilder {
182    fn default() -> Self {
183        Self::new()
184    }
185}