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}