1use std::cmp::max;
5use std::time::Duration;
6
7#[cfg(target_os = "linux")]
8use libc::c_int;
9use tracing::{Span, instrument};
10
11#[cfg(gdb)]
13#[derive(Copy, Clone, Debug, Eq, PartialEq)]
14pub struct DebugInfo {
15 pub port: u16,
17}
18
19#[cfg(target_arch = "x86_64")]
21#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
22pub enum GuestMsrError {
23 #[error("declared guest MSRs exceed the maximum of {maximum} distinct entries")]
25 CapacityExceeded {
26 maximum: usize,
28 },
29}
30
31#[derive(Copy, Clone, Debug, Eq, PartialEq)]
33#[repr(C)]
34pub struct SandboxConfiguration {
35 #[cfg(crashdump)]
42 guest_core_dump: bool,
43 #[cfg(gdb)]
45 guest_debug_info: Option<DebugInfo>,
46 input_data_size: usize,
49 output_data_size: usize,
52 heap_size_override: u64,
59 interrupt_retry_delay: Duration,
66 interrupt_vcpu_sigrtmin_offset: u8,
74 scratch_size: usize,
76 #[cfg(target_arch = "x86_64")]
78 guest_msrs: [u32; Self::MAX_GUEST_MSRS],
79 #[cfg(target_arch = "x86_64")]
81 guest_msrs_count: usize,
82}
83
84impl SandboxConfiguration {
85 pub const DEFAULT_INPUT_SIZE: usize = 0x4000;
87 pub const MIN_INPUT_SIZE: usize = 0x2000;
89 pub const DEFAULT_OUTPUT_SIZE: usize = 0x4000;
91 pub const MIN_OUTPUT_SIZE: usize = 0x2000;
93 pub const DEFAULT_INTERRUPT_RETRY_DELAY: Duration = Duration::from_micros(500);
95 pub const INTERRUPT_VCPU_SIGRTMIN_OFFSET: u8 = 0;
97 pub const DEFAULT_HEAP_SIZE: u64 = 131072;
99 pub const DEFAULT_SCRATCH_SIZE: usize = 0x48000;
101 #[cfg(target_arch = "x86_64")]
105 pub const MAX_GUEST_MSRS: usize = 16;
106
107 #[allow(clippy::too_many_arguments)]
108 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
110 fn new(
111 input_data_size: usize,
112 output_data_size: usize,
113 heap_size_override: Option<u64>,
114 scratch_size: usize,
115 interrupt_retry_delay: Duration,
116 interrupt_vcpu_sigrtmin_offset: u8,
117 #[cfg(gdb)] guest_debug_info: Option<DebugInfo>,
118 #[cfg(crashdump)] guest_core_dump: bool,
119 ) -> Self {
120 Self {
121 input_data_size: max(input_data_size, Self::MIN_INPUT_SIZE),
122 output_data_size: max(output_data_size, Self::MIN_OUTPUT_SIZE),
123 heap_size_override: heap_size_override.unwrap_or(0),
124 scratch_size,
125 interrupt_retry_delay,
126 interrupt_vcpu_sigrtmin_offset,
127 #[cfg(gdb)]
128 guest_debug_info,
129 #[cfg(crashdump)]
130 guest_core_dump,
131 #[cfg(target_arch = "x86_64")]
132 guest_msrs: [0; Self::MAX_GUEST_MSRS],
133 #[cfg(target_arch = "x86_64")]
134 guest_msrs_count: 0,
135 }
136 }
137
138 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
141 pub fn set_input_data_size(&mut self, input_data_size: usize) {
142 self.input_data_size = max(input_data_size, Self::MIN_INPUT_SIZE);
143 }
144
145 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
148 pub fn set_output_data_size(&mut self, output_data_size: usize) {
149 self.output_data_size = max(output_data_size, Self::MIN_OUTPUT_SIZE);
150 }
151
152 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
154 pub fn set_heap_size(&mut self, heap_size: u64) {
155 self.heap_size_override = heap_size;
156 }
157
158 #[cfg(any(kvm, mshv3, hvf))]
160 pub fn set_interrupt_retry_delay(&mut self, delay: Duration) {
161 self.interrupt_retry_delay = delay;
162 }
163
164 #[cfg(any(kvm, mshv3, hvf))]
166 pub fn get_interrupt_retry_delay(&self) -> Duration {
167 self.interrupt_retry_delay
168 }
169
170 #[cfg(target_os = "linux")]
172 pub fn get_interrupt_vcpu_sigrtmin_offset(&self) -> u8 {
173 self.interrupt_vcpu_sigrtmin_offset
174 }
175
176 #[cfg(target_arch = "x86_64")]
202 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
203 pub fn guest_msrs(&mut self, indices: &[u32]) -> Result<&mut Self, GuestMsrError> {
204 let additional = indices
205 .iter()
206 .enumerate()
207 .filter(|(position, index)| {
208 !self.guest_msrs[..self.guest_msrs_count].contains(index)
209 && !indices[..*position].contains(index)
210 })
211 .count();
212 if additional > Self::MAX_GUEST_MSRS - self.guest_msrs_count {
213 return Err(GuestMsrError::CapacityExceeded {
214 maximum: Self::MAX_GUEST_MSRS,
215 });
216 }
217 for &index in indices {
218 if !self.guest_msrs[..self.guest_msrs_count].contains(&index) {
219 self.guest_msrs[self.guest_msrs_count] = index;
220 self.guest_msrs_count += 1;
221 }
222 }
223 Ok(self)
224 }
225
226 #[cfg(target_arch = "x86_64")]
228 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
229 pub(crate) fn get_guest_msrs(&self) -> &[u32] {
230 &self.guest_msrs[..self.guest_msrs_count]
231 }
232
233 #[cfg(target_os = "linux")]
241 pub fn set_interrupt_vcpu_sigrtmin_offset(&mut self, offset: u8) -> crate::Result<()> {
242 if libc::SIGRTMIN() + offset as c_int > libc::SIGRTMAX() {
243 return Err(crate::new_error!(
244 "Invalid SIGRTMIN offset: {}. It exceeds the maximum real-time signal number.",
245 offset
246 ));
247 }
248 self.interrupt_vcpu_sigrtmin_offset = offset;
249 Ok(())
250 }
251
252 #[cfg(crashdump)]
256 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
257 pub fn set_guest_core_dump(&mut self, enable: bool) {
258 self.guest_core_dump = enable;
259 }
260
261 #[cfg(gdb)]
263 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
264 pub fn set_guest_debug_info(&mut self, debug_info: DebugInfo) {
265 self.guest_debug_info = Some(debug_info);
266 }
267
268 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
269 pub(crate) fn get_input_data_size(&self) -> usize {
270 self.input_data_size
271 }
272
273 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
274 pub(crate) fn get_output_data_size(&self) -> usize {
275 self.output_data_size
276 }
277
278 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
279 pub(crate) fn get_scratch_size(&self) -> usize {
280 self.scratch_size
281 }
282
283 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
285 pub fn set_scratch_size(&mut self, scratch_size: usize) {
286 self.scratch_size = scratch_size;
287 }
288
289 #[cfg(crashdump)]
290 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
291 pub(crate) fn get_guest_core_dump(&self) -> bool {
292 self.guest_core_dump
293 }
294
295 #[cfg(gdb)]
296 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
297 pub(crate) fn get_guest_debug_info(&self) -> Option<DebugInfo> {
298 self.guest_debug_info
299 }
300
301 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
302 fn heap_size_override_opt(&self) -> Option<u64> {
303 (self.heap_size_override > 0).then_some(self.heap_size_override)
304 }
305
306 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
309 pub(crate) fn get_heap_size(&self) -> u64 {
310 self.heap_size_override_opt()
311 .unwrap_or(Self::DEFAULT_HEAP_SIZE)
312 }
313}
314
315impl Default for SandboxConfiguration {
316 #[instrument(skip_all, parent = Span::current(), level= "Trace")]
317 fn default() -> Self {
318 Self::new(
319 Self::DEFAULT_INPUT_SIZE,
320 Self::DEFAULT_OUTPUT_SIZE,
321 None,
322 Self::DEFAULT_SCRATCH_SIZE,
323 Self::DEFAULT_INTERRUPT_RETRY_DELAY,
324 Self::INTERRUPT_VCPU_SIGRTMIN_OFFSET,
325 #[cfg(gdb)]
326 None,
327 #[cfg(crashdump)]
328 true,
329 )
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 #[cfg(target_arch = "x86_64")]
336 use super::GuestMsrError;
337 use super::SandboxConfiguration;
338
339 #[test]
340 #[cfg(target_arch = "x86_64")]
341 fn guest_msrs_reports_overflow() {
342 let mut cfg = SandboxConfiguration::default();
343 for index in 0..SandboxConfiguration::MAX_GUEST_MSRS as u32 {
344 cfg.guest_msrs(&[index]).unwrap();
345 }
346
347 cfg.guest_msrs(&[0]).unwrap();
348 assert_eq!(
349 cfg.guest_msrs(&[SandboxConfiguration::MAX_GUEST_MSRS as u32]),
350 Err(GuestMsrError::CapacityExceeded {
351 maximum: SandboxConfiguration::MAX_GUEST_MSRS,
352 })
353 );
354 }
355
356 #[test]
357 #[cfg(target_arch = "x86_64")]
358 fn bulk_guest_msrs_overflow_is_atomic() {
359 let mut cfg = SandboxConfiguration::default();
360 cfg.guest_msrs(&[1, 2]).unwrap();
361 let oversized: Vec<u32> = (3..=SandboxConfiguration::MAX_GUEST_MSRS as u32 + 1).collect();
362
363 assert!(matches!(
364 cfg.guest_msrs(&oversized),
365 Err(GuestMsrError::CapacityExceeded { .. })
366 ));
367 assert_eq!(cfg.get_guest_msrs(), &[1, 2]);
368 }
369
370 #[test]
371 #[cfg(target_arch = "x86_64")]
372 fn guest_msrs_dedups_and_preserves_order() {
373 let mut cfg = SandboxConfiguration::default();
374 cfg.guest_msrs(&[0x10]).unwrap();
375 cfg.guest_msrs(&[0x20, 0x20, 0x10, 0x30, 0x20]).unwrap();
376 assert_eq!(cfg.get_guest_msrs(), &[0x10, 0x20, 0x30]);
378 }
379
380 #[test]
381 #[cfg(target_arch = "x86_64")]
382 fn guest_msrs_duplicates_do_not_count_toward_capacity() {
383 let mut cfg = SandboxConfiguration::default();
384 let fill: Vec<u32> = (0..SandboxConfiguration::MAX_GUEST_MSRS as u32 - 1).collect();
385 cfg.guest_msrs(&fill).unwrap();
386 cfg.guest_msrs(&[u32::MAX, u32::MAX, u32::MAX]).unwrap();
389 assert_eq!(
390 cfg.get_guest_msrs().len(),
391 SandboxConfiguration::MAX_GUEST_MSRS
392 );
393 }
394
395 #[test]
396 fn overrides() {
397 const HEAP_SIZE_OVERRIDE: u64 = 0x50000;
398 const INPUT_DATA_SIZE_OVERRIDE: usize = 0x4000;
399 const OUTPUT_DATA_SIZE_OVERRIDE: usize = 0x4001;
400 const SCRATCH_SIZE_OVERRIDE: usize = 0x60000;
401 let mut cfg = SandboxConfiguration::new(
402 INPUT_DATA_SIZE_OVERRIDE,
403 OUTPUT_DATA_SIZE_OVERRIDE,
404 Some(HEAP_SIZE_OVERRIDE),
405 SCRATCH_SIZE_OVERRIDE,
406 SandboxConfiguration::DEFAULT_INTERRUPT_RETRY_DELAY,
407 SandboxConfiguration::INTERRUPT_VCPU_SIGRTMIN_OFFSET,
408 #[cfg(gdb)]
409 None,
410 #[cfg(crashdump)]
411 true,
412 );
413
414 let heap_size = cfg.get_heap_size();
415 let scratch_size = cfg.get_scratch_size();
416 assert_eq!(HEAP_SIZE_OVERRIDE, heap_size);
417 assert_eq!(SCRATCH_SIZE_OVERRIDE, scratch_size);
418
419 cfg.heap_size_override = 2048;
420 cfg.scratch_size = 0x40000;
421 assert_eq!(2048, cfg.heap_size_override);
422 assert_eq!(0x40000, cfg.scratch_size);
423 assert_eq!(INPUT_DATA_SIZE_OVERRIDE, cfg.input_data_size);
424 assert_eq!(OUTPUT_DATA_SIZE_OVERRIDE, cfg.output_data_size);
425 }
426
427 #[test]
428 fn min_sizes() {
429 let mut cfg = SandboxConfiguration::new(
430 SandboxConfiguration::MIN_INPUT_SIZE - 1,
431 SandboxConfiguration::MIN_OUTPUT_SIZE - 1,
432 None,
433 SandboxConfiguration::DEFAULT_SCRATCH_SIZE,
434 SandboxConfiguration::DEFAULT_INTERRUPT_RETRY_DELAY,
435 SandboxConfiguration::INTERRUPT_VCPU_SIGRTMIN_OFFSET,
436 #[cfg(gdb)]
437 None,
438 #[cfg(crashdump)]
439 true,
440 );
441 assert_eq!(SandboxConfiguration::MIN_INPUT_SIZE, cfg.input_data_size);
442 assert_eq!(SandboxConfiguration::MIN_OUTPUT_SIZE, cfg.output_data_size);
443 assert_eq!(0, cfg.heap_size_override);
444
445 cfg.set_input_data_size(SandboxConfiguration::MIN_INPUT_SIZE - 1);
446 cfg.set_output_data_size(SandboxConfiguration::MIN_OUTPUT_SIZE - 1);
447
448 assert_eq!(SandboxConfiguration::MIN_INPUT_SIZE, cfg.input_data_size);
449 assert_eq!(SandboxConfiguration::MIN_OUTPUT_SIZE, cfg.output_data_size);
450 }
451
452 mod proptests {
453 use proptest::prelude::*;
454
455 use super::SandboxConfiguration;
456 #[cfg(gdb)]
457 use crate::sandbox::config::DebugInfo;
458
459 proptest! {
460 #[test]
461 fn input_data_size(size in SandboxConfiguration::MIN_INPUT_SIZE..=SandboxConfiguration::MIN_INPUT_SIZE * 10) {
462 let mut cfg = SandboxConfiguration::default();
463 cfg.set_input_data_size(size);
464 prop_assert_eq!(size, cfg.get_input_data_size());
465 }
466
467 #[test]
468 fn output_data_size(size in SandboxConfiguration::MIN_OUTPUT_SIZE..=SandboxConfiguration::MIN_OUTPUT_SIZE * 10) {
469 let mut cfg = SandboxConfiguration::default();
470 cfg.set_output_data_size(size);
471 prop_assert_eq!(size, cfg.get_output_data_size());
472 }
473
474
475 #[test]
476 fn heap_size_override(size in 0x1000..=0x10000u64) {
477 let mut cfg = SandboxConfiguration::default();
478 cfg.set_heap_size(size);
479 prop_assert_eq!(size, cfg.heap_size_override);
480 }
481
482 #[test]
483 #[cfg(gdb)]
484 fn guest_debug_info(port in 9000..=u16::MAX) {
485 let mut cfg = SandboxConfiguration::default();
486 let debug_info = DebugInfo { port };
487 cfg.set_guest_debug_info(debug_info);
488 prop_assert_eq!(debug_info, *cfg.get_guest_debug_info().as_ref().unwrap());
489 }
490 }
491 }
492}