Skip to main content

nub_host_kvm/sandbox/
config.rs

1/*
2Copyright 2025  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
17use std::cmp::max;
18use std::time::Duration;
19
20#[cfg(target_os = "linux")]
21use libc::c_int;
22use tracing::{Span, instrument};
23
24/// The complete set of configuration needed to create a Sandbox
25#[derive(Copy, Clone, Debug, Eq, PartialEq)]
26#[repr(C)]
27pub struct SandboxConfiguration {
28    /// The size of the memory buffer that is made available for input to the
29    /// Guest Binary
30    input_data_size: usize,
31    /// The size of the memory buffer that is made available for input to the
32    /// Guest Binary
33    output_data_size: usize,
34    /// The heap size to use in the guest sandbox. If set to 0, the heap
35    /// size will be determined from the PE file header
36    ///
37    /// Note: this is a C-compatible struct, so even though this optional
38    /// field should be represented as an `Option`, that type is not
39    /// FFI-safe, so it cannot be.
40    heap_size_override: u64,
41    /// Delay between interrupt retries. This duration specifies how long to wait
42    /// between attempts to send signals to the thread running the sandbox's VCPU.
43    /// Multiple retries may be necessary because signals only interrupt the VCPU
44    /// thread when the vcpu thread is in kernel space. There's a narrow window during which a
45    /// signal can be delivered to the thread, but the thread may not yet
46    /// have entered kernel space.
47    interrupt_retry_delay: Duration,
48    /// Offset from `SIGRTMIN` used to determine the signal number for interrupting
49    /// the VCPU thread. The actual signal sent is `SIGRTMIN + interrupt_vcpu_sigrtmin_offset`.
50    ///
51    /// This signal must fall within the valid real-time signal range supported by the host.
52    ///
53    /// Note: Since real-time signals can vary across platforms, ensure that the offset
54    /// results in a signal number that is not already in use by other components of the system.
55    interrupt_vcpu_sigrtmin_offset: u8,
56    /// How much writable memory to offer the guest
57    scratch_size: usize,
58    /// Fixed vCPU pool size for this sandbox. The serialized control plane uses
59    /// one selected lane at a time; parallel invokes attach one hot worker to
60    /// each stable vCPU lane.
61    vcpu_count: usize,
62}
63
64impl SandboxConfiguration {
65    /// The default size of input data
66    pub const DEFAULT_INPUT_SIZE: usize = 0x4000;
67    /// The minimum size of input data
68    pub const MIN_INPUT_SIZE: usize = 0x2000;
69    /// The default size of output data
70    pub const DEFAULT_OUTPUT_SIZE: usize = 0x4000;
71    /// The minimum size of output data
72    pub const MIN_OUTPUT_SIZE: usize = 0x2000;
73    /// The default interrupt retry delay
74    pub const DEFAULT_INTERRUPT_RETRY_DELAY: Duration = Duration::from_micros(500);
75    /// The default signal offset from `SIGRTMIN` used to determine the signal number for interrupting
76    pub const INTERRUPT_VCPU_SIGRTMIN_OFFSET: u8 = 0;
77    /// The default heap size of a hyperlight sandbox
78    pub const DEFAULT_HEAP_SIZE: u64 = 131072;
79    /// The default size of the scratch region
80    pub const DEFAULT_SCRATCH_SIZE: usize = 0x48000;
81
82    #[allow(clippy::too_many_arguments)]
83    /// Create a new configuration for a sandbox with the given sizes.
84    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
85    fn new(
86        input_data_size: usize,
87        output_data_size: usize,
88        heap_size_override: Option<u64>,
89        scratch_size: usize,
90        vcpu_count: usize,
91        interrupt_retry_delay: Duration,
92        interrupt_vcpu_sigrtmin_offset: u8,
93    ) -> Self {
94        Self {
95            input_data_size: max(input_data_size, Self::MIN_INPUT_SIZE),
96            output_data_size: max(output_data_size, Self::MIN_OUTPUT_SIZE),
97            heap_size_override: heap_size_override.unwrap_or(0),
98            scratch_size,
99            vcpu_count: vcpu_count.max(1),
100            interrupt_retry_delay,
101            interrupt_vcpu_sigrtmin_offset,
102        }
103    }
104
105    /// Set the size of the memory buffer that is made available for input to the guest
106    /// the minimum value is MIN_INPUT_SIZE
107    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
108    pub fn set_input_data_size(&mut self, input_data_size: usize) {
109        self.input_data_size = max(input_data_size, Self::MIN_INPUT_SIZE);
110    }
111
112    /// Set the size of the memory buffer that is made available for output from the guest
113    /// the minimum value is MIN_OUTPUT_SIZE
114    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
115    pub fn set_output_data_size(&mut self, output_data_size: usize) {
116        self.output_data_size = max(output_data_size, Self::MIN_OUTPUT_SIZE);
117    }
118
119    /// 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
120    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
121    pub fn set_heap_size(&mut self, heap_size: u64) {
122        self.heap_size_override = heap_size;
123    }
124
125    /// Sets the interrupt retry delay
126    #[cfg(target_os = "linux")]
127    pub fn set_interrupt_retry_delay(&mut self, delay: Duration) {
128        self.interrupt_retry_delay = delay;
129    }
130
131    /// Get the delay between retries for interrupts
132    #[cfg(target_os = "linux")]
133    pub fn get_interrupt_retry_delay(&self) -> Duration {
134        self.interrupt_retry_delay
135    }
136
137    /// Get the signal offset from `SIGRTMIN` used to determine the signal number for interrupting the VCPU thread
138    #[cfg(target_os = "linux")]
139    pub fn get_interrupt_vcpu_sigrtmin_offset(&self) -> u8 {
140        self.interrupt_vcpu_sigrtmin_offset
141    }
142
143    /// Sets the offset from `SIGRTMIN` to determine the real-time signal used for
144    /// interrupting the VCPU thread.
145    ///
146    /// The final signal number is computed as `SIGRTMIN + offset`, and it must fall within
147    /// the valid range of real-time signals supported by the host system.
148    ///
149    /// Returns Ok(()) if the offset is valid, or an error if it exceeds the maximum real-time signal number.
150    #[cfg(target_os = "linux")]
151    pub fn set_interrupt_vcpu_sigrtmin_offset(&mut self, offset: u8) -> crate::Result<()> {
152        if libc::SIGRTMIN() + offset as c_int > libc::SIGRTMAX() {
153            return Err(crate::new_error!(
154                "Invalid SIGRTMIN offset: {}. It exceeds the maximum real-time signal number.",
155                offset
156            ));
157        }
158        self.interrupt_vcpu_sigrtmin_offset = offset;
159        Ok(())
160    }
161
162    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
163    pub(crate) fn get_input_data_size(&self) -> usize {
164        self.input_data_size
165    }
166
167    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
168    pub(crate) fn get_output_data_size(&self) -> usize {
169        self.output_data_size
170    }
171
172    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
173    pub(crate) fn get_scratch_size(&self) -> usize {
174        self.scratch_size
175    }
176
177    /// Set the size of the scratch regiong
178    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
179    pub fn set_scratch_size(&mut self, scratch_size: usize) {
180        self.scratch_size = scratch_size;
181    }
182
183    /// Set the fixed vCPU pool size for this sandbox.
184    pub fn set_vcpu_count(&mut self, vcpu_count: usize) {
185        self.vcpu_count = vcpu_count.max(1);
186    }
187
188    pub(crate) fn get_vcpu_count(&self) -> usize {
189        self.vcpu_count
190    }
191
192    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
193    fn heap_size_override_opt(&self) -> Option<u64> {
194        (self.heap_size_override > 0).then_some(self.heap_size_override)
195    }
196
197    /// If self.heap_size_override is non-zero, return it. Otherwise,
198    /// return exe_info.heap_reserve()
199    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
200    pub(crate) fn get_heap_size(&self) -> u64 {
201        self.heap_size_override_opt()
202            .unwrap_or(Self::DEFAULT_HEAP_SIZE)
203    }
204}
205
206impl Default for SandboxConfiguration {
207    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
208    fn default() -> Self {
209        Self::new(
210            Self::DEFAULT_INPUT_SIZE,
211            Self::DEFAULT_OUTPUT_SIZE,
212            None,
213            Self::DEFAULT_SCRATCH_SIZE,
214            1,
215            Self::DEFAULT_INTERRUPT_RETRY_DELAY,
216            Self::INTERRUPT_VCPU_SIGRTMIN_OFFSET,
217        )
218    }
219}