hyperlight_host/sandbox/
config.rs

1/*
2Copyright 2024 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, min};
18use std::time::Duration;
19
20use tracing::{instrument, Span};
21
22use crate::mem::exe::ExeInfo;
23
24/// Used for passing debug configuration to a sandbox
25#[cfg(gdb)]
26#[derive(Copy, Clone, Debug, Eq, PartialEq)]
27pub struct DebugInfo {
28    /// Guest debug port
29    pub port: u16,
30}
31
32/// The complete set of configuration needed to create a Sandbox
33#[derive(Copy, Clone, Debug, Eq, PartialEq)]
34#[repr(C)]
35pub struct SandboxConfiguration {
36    /// Guest gdb debug port
37    #[cfg(gdb)]
38    guest_debug_info: Option<DebugInfo>,
39    /// The size of the memory buffer that is made available for input to the
40    /// Guest Binary
41    input_data_size: usize,
42    /// The size of the memory buffer that is made available for input to the
43    /// Guest Binary
44    output_data_size: usize,
45    /// The stack size to use in the guest sandbox. If set to 0, the stack
46    /// size will be determined from the PE file header.
47    ///
48    /// Note: this is a C-compatible struct, so even though this optional
49    /// field should be represented as an `Option`, that type is not
50    /// FFI-safe, so it cannot be.
51    stack_size_override: u64,
52    /// The heap size to use in the guest sandbox. If set to 0, the heap
53    /// size will be determined from the PE file header
54    ///
55    /// Note: this is a C-compatible struct, so even though this optional
56    /// field should be represented as an `Option`, that type is not
57    /// FFI-safe, so it cannot be.
58    heap_size_override: u64,
59    /// The max_execution_time of a guest execution in milliseconds. If set to 0, the max_execution_time
60    /// will be set to the default value of 1000ms if the guest execution does not complete within the time specified
61    /// then the execution will be cancelled, the minimum value is 1ms
62    ///
63    /// Note: this is a C-compatible struct, so even though this optional
64    /// field should be represented as an `Option`, that type is not
65    /// FFI-safe, so it cannot be.
66    ///
67    max_execution_time: u16,
68    /// The max_wait_for_cancellation represents the maximum time the host should wait for a guest execution to be cancelled
69    /// If set to 0, the max_wait_for_cancellation will be set to the default value of 10ms.
70    /// The minimum value is 1ms.
71    ///
72    /// Note: this is a C-compatible struct, so even though this optional
73    /// field should be represented as an `Option`, that type is not
74    /// FFI-safe, so it cannot be.
75    max_wait_for_cancellation: u8,
76    // The max_initialization_time represents the maximum time the host should wait for a guest to initialize
77    // If set to 0, the max_initialization_time will be set to the default value of 2000ms.
78    // The minimum value is 1ms.
79    //
80    // Note: this is a C-compatible struct, so even though this optional
81    // field should be represented as an `Option`, that type is not
82    // FFI-safe, so it cannot be.
83    max_initialization_time: u16,
84}
85
86impl SandboxConfiguration {
87    /// The default size of input data
88    pub const DEFAULT_INPUT_SIZE: usize = 0x4000;
89    /// The minimum size of input data
90    pub const MIN_INPUT_SIZE: usize = 0x2000;
91    /// The default size of output data
92    pub const DEFAULT_OUTPUT_SIZE: usize = 0x4000;
93    /// The minimum size of output data
94    pub const MIN_OUTPUT_SIZE: usize = 0x2000;
95    /// The default value for max initialization time (in milliseconds)
96    pub const DEFAULT_MAX_INITIALIZATION_TIME: u16 = 2000;
97    /// The minimum value for max initialization time (in milliseconds)
98    pub const MIN_MAX_INITIALIZATION_TIME: u16 = 1;
99    /// The maximum value for max initialization time (in milliseconds)
100    pub const MAX_MAX_INITIALIZATION_TIME: u16 = u16::MAX;
101    /// The default and minimum values for max execution time (in milliseconds)
102    pub const DEFAULT_MAX_EXECUTION_TIME: u16 = 1000;
103    /// The minimum value for max execution time (in milliseconds)
104    pub const MIN_MAX_EXECUTION_TIME: u16 = 1;
105    /// The maximum value for max execution time (in milliseconds)
106    pub const MAX_MAX_EXECUTION_TIME: u16 = u16::MAX;
107    /// The default and minimum values for max wait for cancellation (in milliseconds)
108    pub const DEFAULT_MAX_WAIT_FOR_CANCELLATION: u8 = 100;
109    /// The minimum value for max wait for cancellation (in milliseconds)
110    pub const MIN_MAX_WAIT_FOR_CANCELLATION: u8 = 10;
111    /// The maximum value for max wait for cancellation (in milliseconds)
112    pub const MAX_MAX_WAIT_FOR_CANCELLATION: u8 = u8::MAX;
113
114    #[allow(clippy::too_many_arguments)]
115    /// Create a new configuration for a sandbox with the given sizes.
116    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
117    fn new(
118        input_data_size: usize,
119        output_data_size: usize,
120        stack_size_override: Option<u64>,
121        heap_size_override: Option<u64>,
122        max_execution_time: Option<Duration>,
123        max_initialization_time: Option<Duration>,
124        max_wait_for_cancellation: Option<Duration>,
125        #[cfg(gdb)] guest_debug_info: Option<DebugInfo>,
126    ) -> Self {
127        Self {
128            input_data_size: max(input_data_size, Self::MIN_INPUT_SIZE),
129            output_data_size: max(output_data_size, Self::MIN_OUTPUT_SIZE),
130            stack_size_override: stack_size_override.unwrap_or(0),
131            heap_size_override: heap_size_override.unwrap_or(0),
132            max_execution_time: {
133                match max_execution_time {
134                    Some(max_execution_time) => match max_execution_time.as_millis() {
135                        0 => Self::DEFAULT_MAX_EXECUTION_TIME,
136                        1.. => min(
137                            Self::MAX_MAX_EXECUTION_TIME.into(),
138                            max(
139                                max_execution_time.as_millis(),
140                                Self::MIN_MAX_EXECUTION_TIME.into(),
141                            ),
142                        ) as u16,
143                    },
144                    None => Self::DEFAULT_MAX_EXECUTION_TIME,
145                }
146            },
147            max_wait_for_cancellation: {
148                match max_wait_for_cancellation {
149                    Some(max_wait_for_cancellation) => {
150                        match max_wait_for_cancellation.as_millis() {
151                            0 => Self::DEFAULT_MAX_WAIT_FOR_CANCELLATION,
152                            1.. => min(
153                                Self::MAX_MAX_WAIT_FOR_CANCELLATION.into(),
154                                max(
155                                    max_wait_for_cancellation.as_millis(),
156                                    Self::MIN_MAX_WAIT_FOR_CANCELLATION.into(),
157                                ),
158                            ) as u8,
159                        }
160                    }
161                    None => Self::DEFAULT_MAX_WAIT_FOR_CANCELLATION,
162                }
163            },
164            max_initialization_time: {
165                match max_initialization_time {
166                    Some(max_initialization_time) => match max_initialization_time.as_millis() {
167                        0 => Self::DEFAULT_MAX_INITIALIZATION_TIME,
168                        1.. => min(
169                            Self::MAX_MAX_INITIALIZATION_TIME.into(),
170                            max(
171                                max_initialization_time.as_millis(),
172                                Self::MIN_MAX_INITIALIZATION_TIME.into(),
173                            ),
174                        ) as u16,
175                    },
176                    None => Self::DEFAULT_MAX_INITIALIZATION_TIME,
177                }
178            },
179            #[cfg(gdb)]
180            guest_debug_info,
181        }
182    }
183
184    /// Set the size of the memory buffer that is made available for input to the guest
185    /// the minimum value is MIN_INPUT_SIZE
186    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
187    pub fn set_input_data_size(&mut self, input_data_size: usize) {
188        self.input_data_size = max(input_data_size, Self::MIN_INPUT_SIZE);
189    }
190
191    /// Set the size of the memory buffer that is made available for output from the guest
192    /// the minimum value is MIN_OUTPUT_SIZE
193    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
194    pub fn set_output_data_size(&mut self, output_data_size: usize) {
195        self.output_data_size = max(output_data_size, Self::MIN_OUTPUT_SIZE);
196    }
197
198    /// 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
199    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
200    pub fn set_stack_size(&mut self, stack_size: u64) {
201        self.stack_size_override = stack_size;
202    }
203
204    /// 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
205    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
206    pub fn set_heap_size(&mut self, heap_size: u64) {
207        self.heap_size_override = heap_size;
208    }
209
210    /// Set the maximum execution time of a guest function execution. If set to 0, the max_execution_time
211    /// will be set to the default value of DEFAULT_MAX_EXECUTION_TIME if the guest execution does not complete within the time specified
212    /// then the execution will be cancelled, the minimum value is MIN_MAX_EXECUTION_TIME
213    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
214    pub fn set_max_execution_time(&mut self, max_execution_time: Duration) {
215        match max_execution_time.as_millis() {
216            0 => self.max_execution_time = Self::DEFAULT_MAX_EXECUTION_TIME,
217            1.. => {
218                self.max_execution_time = min(
219                    Self::MAX_MAX_EXECUTION_TIME.into(),
220                    max(
221                        max_execution_time.as_millis(),
222                        Self::MIN_MAX_EXECUTION_TIME.into(),
223                    ),
224                ) as u16
225            }
226        }
227    }
228
229    /// Set the maximum time to wait for guest execution calculation. If set to 0, the maximum cancellation time
230    /// will be set to the default value of DEFAULT_MAX_WAIT_FOR_CANCELLATION if the guest execution cancellation does not complete within the time specified
231    /// then an error will be returned, the minimum value is MIN_MAX_WAIT_FOR_CANCELLATION
232    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
233    pub fn set_max_execution_cancel_wait_time(&mut self, max_wait_for_cancellation: Duration) {
234        match max_wait_for_cancellation.as_millis() {
235            0 => self.max_wait_for_cancellation = Self::DEFAULT_MAX_WAIT_FOR_CANCELLATION,
236            1.. => {
237                self.max_wait_for_cancellation = min(
238                    Self::MAX_MAX_WAIT_FOR_CANCELLATION.into(),
239                    max(
240                        max_wait_for_cancellation.as_millis(),
241                        Self::MIN_MAX_WAIT_FOR_CANCELLATION.into(),
242                    ),
243                ) as u8
244            }
245        }
246    }
247
248    /// Set the maximum time to wait for guest initialization. If set to 0, the maximum initialization time
249    /// will be set to the default value of DEFAULT_MAX_INITIALIZATION_TIME if the guest initialization does not complete within the time specified
250    /// then an error will be returned, the minimum value is MIN_MAX_INITIALIZATION_TIME
251    pub fn set_max_initialization_time(&mut self, max_initialization_time: Duration) {
252        match max_initialization_time.as_millis() {
253            0 => self.max_initialization_time = Self::DEFAULT_MAX_INITIALIZATION_TIME,
254            1.. => {
255                self.max_initialization_time = min(
256                    Self::MAX_MAX_INITIALIZATION_TIME.into(),
257                    max(
258                        max_initialization_time.as_millis(),
259                        Self::MIN_MAX_INITIALIZATION_TIME.into(),
260                    ),
261                ) as u16
262            }
263        }
264    }
265
266    /// Sets the configuration for the guest debug
267    #[cfg(gdb)]
268    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
269    pub fn set_guest_debug_info(&mut self, debug_info: DebugInfo) {
270        self.guest_debug_info = Some(debug_info);
271    }
272
273    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
274    pub(crate) fn get_input_data_size(&self) -> usize {
275        self.input_data_size
276    }
277
278    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
279    pub(crate) fn get_output_data_size(&self) -> usize {
280        self.output_data_size
281    }
282
283    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
284    pub(crate) fn get_max_execution_time(&self) -> u16 {
285        self.max_execution_time
286    }
287
288    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
289    pub(crate) fn get_max_wait_for_cancellation(&self) -> u8 {
290        self.max_wait_for_cancellation
291    }
292
293    pub(crate) fn get_max_initialization_time(&self) -> u16 {
294        self.max_initialization_time
295    }
296
297    #[cfg(gdb)]
298    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
299    pub(crate) fn get_guest_debug_info(&self) -> Option<DebugInfo> {
300        self.guest_debug_info
301    }
302
303    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
304    fn stack_size_override_opt(&self) -> Option<u64> {
305        (self.stack_size_override > 0).then_some(self.stack_size_override)
306    }
307
308    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
309    fn heap_size_override_opt(&self) -> Option<u64> {
310        (self.heap_size_override > 0).then_some(self.heap_size_override)
311    }
312
313    /// If self.stack_size is non-zero, return it. Otherwise,
314    /// return exe_info.stack_reserve()
315    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
316    pub(crate) fn get_stack_size(&self, exe_info: &ExeInfo) -> u64 {
317        self.stack_size_override_opt()
318            .unwrap_or_else(|| exe_info.stack_reserve())
319    }
320
321    /// If self.heap_size_override is non-zero, return it. Otherwise,
322    /// return exe_info.heap_reserve()
323    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
324    pub(crate) fn get_heap_size(&self, exe_info: &ExeInfo) -> u64 {
325        self.heap_size_override_opt()
326            .unwrap_or_else(|| exe_info.heap_reserve())
327    }
328}
329
330impl Default for SandboxConfiguration {
331    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
332    fn default() -> Self {
333        Self::new(
334            Self::DEFAULT_INPUT_SIZE,
335            Self::DEFAULT_OUTPUT_SIZE,
336            None,
337            None,
338            None,
339            None,
340            None,
341            #[cfg(gdb)]
342            None,
343        )
344    }
345}
346
347#[cfg(test)]
348mod tests {
349    use std::time::Duration;
350
351    use super::SandboxConfiguration;
352    use crate::testing::simple_guest_exe_info;
353
354    #[test]
355    fn overrides() {
356        const STACK_SIZE_OVERRIDE: u64 = 0x10000;
357        const HEAP_SIZE_OVERRIDE: u64 = 0x50000;
358        const INPUT_DATA_SIZE_OVERRIDE: usize = 0x4000;
359        const OUTPUT_DATA_SIZE_OVERRIDE: usize = 0x4001;
360        const MAX_EXECUTION_TIME_OVERRIDE: u16 = 1010;
361        const MAX_WAIT_FOR_CANCELLATION_OVERRIDE: u8 = 200;
362        const MAX_INITIALIZATION_TIME_OVERRIDE: u16 = 2000;
363        let mut cfg = SandboxConfiguration::new(
364            INPUT_DATA_SIZE_OVERRIDE,
365            OUTPUT_DATA_SIZE_OVERRIDE,
366            Some(STACK_SIZE_OVERRIDE),
367            Some(HEAP_SIZE_OVERRIDE),
368            Some(Duration::from_millis(MAX_EXECUTION_TIME_OVERRIDE as u64)),
369            Some(Duration::from_millis(
370                MAX_INITIALIZATION_TIME_OVERRIDE as u64,
371            )),
372            Some(Duration::from_millis(
373                MAX_WAIT_FOR_CANCELLATION_OVERRIDE as u64,
374            )),
375            #[cfg(gdb)]
376            None,
377        );
378        let exe_info = simple_guest_exe_info().unwrap();
379
380        let stack_size = cfg.get_stack_size(&exe_info);
381        let heap_size = cfg.get_heap_size(&exe_info);
382        assert_eq!(STACK_SIZE_OVERRIDE, stack_size);
383        assert_eq!(HEAP_SIZE_OVERRIDE, heap_size);
384
385        cfg.stack_size_override = 1024;
386        cfg.heap_size_override = 2048;
387        assert_eq!(1024, cfg.stack_size_override);
388        assert_eq!(2048, cfg.heap_size_override);
389        assert_eq!(INPUT_DATA_SIZE_OVERRIDE, cfg.input_data_size);
390        assert_eq!(OUTPUT_DATA_SIZE_OVERRIDE, cfg.output_data_size);
391        assert_eq!(MAX_EXECUTION_TIME_OVERRIDE, cfg.max_execution_time);
392        assert_eq!(
393            MAX_WAIT_FOR_CANCELLATION_OVERRIDE,
394            cfg.max_wait_for_cancellation
395        );
396        assert_eq!(
397            MAX_WAIT_FOR_CANCELLATION_OVERRIDE,
398            cfg.max_wait_for_cancellation
399        );
400    }
401
402    #[test]
403    fn min_sizes() {
404        let mut cfg = SandboxConfiguration::new(
405            SandboxConfiguration::MIN_INPUT_SIZE - 1,
406            SandboxConfiguration::MIN_OUTPUT_SIZE - 1,
407            None,
408            None,
409            Some(Duration::from_millis(
410                SandboxConfiguration::MIN_MAX_EXECUTION_TIME as u64,
411            )),
412            Some(Duration::from_millis(
413                SandboxConfiguration::MIN_MAX_INITIALIZATION_TIME as u64,
414            )),
415            Some(Duration::from_millis(
416                SandboxConfiguration::MIN_MAX_WAIT_FOR_CANCELLATION as u64 - 1,
417            )),
418            #[cfg(gdb)]
419            None,
420        );
421        assert_eq!(SandboxConfiguration::MIN_INPUT_SIZE, cfg.input_data_size);
422        assert_eq!(SandboxConfiguration::MIN_OUTPUT_SIZE, cfg.output_data_size);
423        assert_eq!(0, cfg.stack_size_override);
424        assert_eq!(0, cfg.heap_size_override);
425        assert_eq!(
426            SandboxConfiguration::MIN_MAX_EXECUTION_TIME,
427            cfg.max_execution_time
428        );
429        assert_eq!(
430            SandboxConfiguration::MIN_MAX_WAIT_FOR_CANCELLATION,
431            cfg.max_wait_for_cancellation
432        );
433        assert_eq!(
434            SandboxConfiguration::MIN_MAX_EXECUTION_TIME,
435            cfg.max_initialization_time
436        );
437
438        cfg.set_input_data_size(SandboxConfiguration::MIN_INPUT_SIZE - 1);
439        cfg.set_output_data_size(SandboxConfiguration::MIN_OUTPUT_SIZE - 1);
440        cfg.set_max_execution_time(Duration::from_millis(
441            SandboxConfiguration::MIN_MAX_EXECUTION_TIME as u64,
442        ));
443        cfg.set_max_initialization_time(Duration::from_millis(
444            SandboxConfiguration::MIN_MAX_INITIALIZATION_TIME as u64 - 1,
445        ));
446        cfg.set_max_execution_cancel_wait_time(Duration::from_millis(
447            SandboxConfiguration::MIN_MAX_WAIT_FOR_CANCELLATION as u64 - 1,
448        ));
449
450        assert_eq!(SandboxConfiguration::MIN_INPUT_SIZE, cfg.input_data_size);
451        assert_eq!(SandboxConfiguration::MIN_OUTPUT_SIZE, cfg.output_data_size);
452        assert_eq!(
453            SandboxConfiguration::MIN_MAX_EXECUTION_TIME,
454            cfg.max_execution_time
455        );
456        assert_eq!(
457            SandboxConfiguration::MIN_MAX_WAIT_FOR_CANCELLATION,
458            cfg.max_wait_for_cancellation
459        );
460    }
461
462    mod proptests {
463        use proptest::prelude::*;
464
465        use super::SandboxConfiguration;
466        #[cfg(gdb)]
467        use crate::sandbox::config::DebugInfo;
468
469        proptest! {
470            #[test]
471            fn input_data_size(size in SandboxConfiguration::MIN_INPUT_SIZE..=SandboxConfiguration::MIN_INPUT_SIZE * 10) {
472                let mut cfg = SandboxConfiguration::default();
473                cfg.set_input_data_size(size);
474                prop_assert_eq!(size, cfg.get_input_data_size());
475            }
476
477            #[test]
478            fn output_data_size(size in SandboxConfiguration::MIN_OUTPUT_SIZE..=SandboxConfiguration::MIN_OUTPUT_SIZE * 10) {
479                let mut cfg = SandboxConfiguration::default();
480                cfg.set_output_data_size(size);
481                prop_assert_eq!(size, cfg.get_output_data_size());
482            }
483
484            #[test]
485            fn max_execution_time(time in SandboxConfiguration::MIN_MAX_EXECUTION_TIME..=SandboxConfiguration::MIN_MAX_EXECUTION_TIME * 10) {
486                let mut cfg = SandboxConfiguration::default();
487                cfg.set_max_execution_time(std::time::Duration::from_millis(time.into()));
488                prop_assert_eq!(time, cfg.get_max_execution_time());
489            }
490
491            #[test]
492            fn max_wait_for_cancellation(time in SandboxConfiguration::MIN_MAX_WAIT_FOR_CANCELLATION..=SandboxConfiguration::MIN_MAX_WAIT_FOR_CANCELLATION * 10) {
493                let mut cfg = SandboxConfiguration::default();
494                cfg.set_max_execution_cancel_wait_time(std::time::Duration::from_millis(time.into()));
495                prop_assert_eq!(time, cfg.get_max_wait_for_cancellation());
496            }
497
498            #[test]
499            fn max_initialization_time(time in SandboxConfiguration::MIN_MAX_INITIALIZATION_TIME..=SandboxConfiguration::MIN_MAX_INITIALIZATION_TIME * 10) {
500                let mut cfg = SandboxConfiguration::default();
501                cfg.set_max_initialization_time(std::time::Duration::from_millis(time.into()));
502                prop_assert_eq!(time, cfg.get_max_initialization_time());
503            }
504
505            #[test]
506            fn stack_size_override(size in 0x1000..=0x10000u64) {
507                let mut cfg = SandboxConfiguration::default();
508                cfg.set_stack_size(size);
509                prop_assert_eq!(size, cfg.stack_size_override);
510            }
511
512            #[test]
513            fn heap_size_override(size in 0x1000..=0x10000u64) {
514                let mut cfg = SandboxConfiguration::default();
515                cfg.set_heap_size(size);
516                prop_assert_eq!(size, cfg.heap_size_override);
517            }
518
519            #[test]
520            #[cfg(gdb)]
521            fn guest_debug_info(port in 9000..=u16::MAX) {
522                let mut cfg = SandboxConfiguration::default();
523                let debug_info = DebugInfo { port };
524                cfg.set_guest_debug_info(debug_info);
525                prop_assert_eq!(debug_info, *cfg.get_guest_debug_info().as_ref().unwrap());
526            }
527        }
528    }
529}