use std::cmp::max;
use std::time::Duration;
#[cfg(target_os = "linux")]
use libc::c_int;
use tracing::{Span, instrument};
#[cfg(gdb)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct DebugInfo {
pub port: u16,
}
#[cfg(target_arch = "x86_64")]
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
pub enum GuestMsrError {
#[error("declared guest MSRs exceed the maximum of {maximum} distinct entries")]
CapacityExceeded {
maximum: usize,
},
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(C)]
pub struct SandboxConfiguration {
#[cfg(crashdump)]
guest_core_dump: bool,
#[cfg(gdb)]
guest_debug_info: Option<DebugInfo>,
input_data_size: usize,
output_data_size: usize,
heap_size_override: u64,
interrupt_retry_delay: Duration,
interrupt_vcpu_sigrtmin_offset: u8,
scratch_size: usize,
#[cfg(target_arch = "x86_64")]
guest_msrs: [u32; Self::MAX_GUEST_MSRS],
#[cfg(target_arch = "x86_64")]
guest_msrs_count: usize,
}
impl SandboxConfiguration {
pub const DEFAULT_INPUT_SIZE: usize = 0x4000;
pub const MIN_INPUT_SIZE: usize = 0x2000;
pub const DEFAULT_OUTPUT_SIZE: usize = 0x4000;
pub const MIN_OUTPUT_SIZE: usize = 0x2000;
pub const DEFAULT_INTERRUPT_RETRY_DELAY: Duration = Duration::from_micros(500);
pub const INTERRUPT_VCPU_SIGRTMIN_OFFSET: u8 = 0;
pub const DEFAULT_HEAP_SIZE: u64 = 131072;
pub const DEFAULT_SCRATCH_SIZE: usize = 0x48000;
#[cfg(target_arch = "x86_64")]
pub const MAX_GUEST_MSRS: usize = 16;
#[allow(clippy::too_many_arguments)]
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
fn new(
input_data_size: usize,
output_data_size: usize,
heap_size_override: Option<u64>,
scratch_size: usize,
interrupt_retry_delay: Duration,
interrupt_vcpu_sigrtmin_offset: u8,
#[cfg(gdb)] guest_debug_info: Option<DebugInfo>,
#[cfg(crashdump)] guest_core_dump: bool,
) -> Self {
Self {
input_data_size: max(input_data_size, Self::MIN_INPUT_SIZE),
output_data_size: max(output_data_size, Self::MIN_OUTPUT_SIZE),
heap_size_override: heap_size_override.unwrap_or(0),
scratch_size,
interrupt_retry_delay,
interrupt_vcpu_sigrtmin_offset,
#[cfg(gdb)]
guest_debug_info,
#[cfg(crashdump)]
guest_core_dump,
#[cfg(target_arch = "x86_64")]
guest_msrs: [0; Self::MAX_GUEST_MSRS],
#[cfg(target_arch = "x86_64")]
guest_msrs_count: 0,
}
}
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
pub fn set_input_data_size(&mut self, input_data_size: usize) {
self.input_data_size = max(input_data_size, Self::MIN_INPUT_SIZE);
}
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
pub fn set_output_data_size(&mut self, output_data_size: usize) {
self.output_data_size = max(output_data_size, Self::MIN_OUTPUT_SIZE);
}
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
pub fn set_heap_size(&mut self, heap_size: u64) {
self.heap_size_override = heap_size;
}
#[cfg(any(kvm, mshv3, hvf))]
pub fn set_interrupt_retry_delay(&mut self, delay: Duration) {
self.interrupt_retry_delay = delay;
}
#[cfg(any(kvm, mshv3, hvf))]
pub fn get_interrupt_retry_delay(&self) -> Duration {
self.interrupt_retry_delay
}
#[cfg(target_os = "linux")]
pub fn get_interrupt_vcpu_sigrtmin_offset(&self) -> u8 {
self.interrupt_vcpu_sigrtmin_offset
}
#[cfg(target_arch = "x86_64")]
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
pub fn guest_msrs(&mut self, indices: &[u32]) -> Result<&mut Self, GuestMsrError> {
let additional = indices
.iter()
.enumerate()
.filter(|(position, index)| {
!self.guest_msrs[..self.guest_msrs_count].contains(index)
&& !indices[..*position].contains(index)
})
.count();
if additional > Self::MAX_GUEST_MSRS - self.guest_msrs_count {
return Err(GuestMsrError::CapacityExceeded {
maximum: Self::MAX_GUEST_MSRS,
});
}
for &index in indices {
if !self.guest_msrs[..self.guest_msrs_count].contains(&index) {
self.guest_msrs[self.guest_msrs_count] = index;
self.guest_msrs_count += 1;
}
}
Ok(self)
}
#[cfg(target_arch = "x86_64")]
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
pub(crate) fn get_guest_msrs(&self) -> &[u32] {
&self.guest_msrs[..self.guest_msrs_count]
}
#[cfg(target_os = "linux")]
pub fn set_interrupt_vcpu_sigrtmin_offset(&mut self, offset: u8) -> crate::Result<()> {
if libc::SIGRTMIN() + offset as c_int > libc::SIGRTMAX() {
return Err(crate::new_error!(
"Invalid SIGRTMIN offset: {}. It exceeds the maximum real-time signal number.",
offset
));
}
self.interrupt_vcpu_sigrtmin_offset = offset;
Ok(())
}
#[cfg(crashdump)]
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
pub fn set_guest_core_dump(&mut self, enable: bool) {
self.guest_core_dump = enable;
}
#[cfg(gdb)]
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
pub fn set_guest_debug_info(&mut self, debug_info: DebugInfo) {
self.guest_debug_info = Some(debug_info);
}
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
pub(crate) fn get_input_data_size(&self) -> usize {
self.input_data_size
}
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
pub(crate) fn get_output_data_size(&self) -> usize {
self.output_data_size
}
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
pub(crate) fn get_scratch_size(&self) -> usize {
self.scratch_size
}
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
pub fn set_scratch_size(&mut self, scratch_size: usize) {
self.scratch_size = scratch_size;
}
#[cfg(crashdump)]
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
pub(crate) fn get_guest_core_dump(&self) -> bool {
self.guest_core_dump
}
#[cfg(gdb)]
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
pub(crate) fn get_guest_debug_info(&self) -> Option<DebugInfo> {
self.guest_debug_info
}
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
fn heap_size_override_opt(&self) -> Option<u64> {
(self.heap_size_override > 0).then_some(self.heap_size_override)
}
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
pub(crate) fn get_heap_size(&self) -> u64 {
self.heap_size_override_opt()
.unwrap_or(Self::DEFAULT_HEAP_SIZE)
}
}
impl Default for SandboxConfiguration {
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
fn default() -> Self {
Self::new(
Self::DEFAULT_INPUT_SIZE,
Self::DEFAULT_OUTPUT_SIZE,
None,
Self::DEFAULT_SCRATCH_SIZE,
Self::DEFAULT_INTERRUPT_RETRY_DELAY,
Self::INTERRUPT_VCPU_SIGRTMIN_OFFSET,
#[cfg(gdb)]
None,
#[cfg(crashdump)]
true,
)
}
}
#[cfg(test)]
mod tests {
#[cfg(target_arch = "x86_64")]
use super::GuestMsrError;
use super::SandboxConfiguration;
#[test]
#[cfg(target_arch = "x86_64")]
fn guest_msrs_reports_overflow() {
let mut cfg = SandboxConfiguration::default();
for index in 0..SandboxConfiguration::MAX_GUEST_MSRS as u32 {
cfg.guest_msrs(&[index]).unwrap();
}
cfg.guest_msrs(&[0]).unwrap();
assert_eq!(
cfg.guest_msrs(&[SandboxConfiguration::MAX_GUEST_MSRS as u32]),
Err(GuestMsrError::CapacityExceeded {
maximum: SandboxConfiguration::MAX_GUEST_MSRS,
})
);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn bulk_guest_msrs_overflow_is_atomic() {
let mut cfg = SandboxConfiguration::default();
cfg.guest_msrs(&[1, 2]).unwrap();
let oversized: Vec<u32> = (3..=SandboxConfiguration::MAX_GUEST_MSRS as u32 + 1).collect();
assert!(matches!(
cfg.guest_msrs(&oversized),
Err(GuestMsrError::CapacityExceeded { .. })
));
assert_eq!(cfg.get_guest_msrs(), &[1, 2]);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn guest_msrs_dedups_and_preserves_order() {
let mut cfg = SandboxConfiguration::default();
cfg.guest_msrs(&[0x10]).unwrap();
cfg.guest_msrs(&[0x20, 0x20, 0x10, 0x30, 0x20]).unwrap();
assert_eq!(cfg.get_guest_msrs(), &[0x10, 0x20, 0x30]);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn guest_msrs_duplicates_do_not_count_toward_capacity() {
let mut cfg = SandboxConfiguration::default();
let fill: Vec<u32> = (0..SandboxConfiguration::MAX_GUEST_MSRS as u32 - 1).collect();
cfg.guest_msrs(&fill).unwrap();
cfg.guest_msrs(&[u32::MAX, u32::MAX, u32::MAX]).unwrap();
assert_eq!(
cfg.get_guest_msrs().len(),
SandboxConfiguration::MAX_GUEST_MSRS
);
}
#[test]
fn overrides() {
const HEAP_SIZE_OVERRIDE: u64 = 0x50000;
const INPUT_DATA_SIZE_OVERRIDE: usize = 0x4000;
const OUTPUT_DATA_SIZE_OVERRIDE: usize = 0x4001;
const SCRATCH_SIZE_OVERRIDE: usize = 0x60000;
let mut cfg = SandboxConfiguration::new(
INPUT_DATA_SIZE_OVERRIDE,
OUTPUT_DATA_SIZE_OVERRIDE,
Some(HEAP_SIZE_OVERRIDE),
SCRATCH_SIZE_OVERRIDE,
SandboxConfiguration::DEFAULT_INTERRUPT_RETRY_DELAY,
SandboxConfiguration::INTERRUPT_VCPU_SIGRTMIN_OFFSET,
#[cfg(gdb)]
None,
#[cfg(crashdump)]
true,
);
let heap_size = cfg.get_heap_size();
let scratch_size = cfg.get_scratch_size();
assert_eq!(HEAP_SIZE_OVERRIDE, heap_size);
assert_eq!(SCRATCH_SIZE_OVERRIDE, scratch_size);
cfg.heap_size_override = 2048;
cfg.scratch_size = 0x40000;
assert_eq!(2048, cfg.heap_size_override);
assert_eq!(0x40000, cfg.scratch_size);
assert_eq!(INPUT_DATA_SIZE_OVERRIDE, cfg.input_data_size);
assert_eq!(OUTPUT_DATA_SIZE_OVERRIDE, cfg.output_data_size);
}
#[test]
fn min_sizes() {
let mut cfg = SandboxConfiguration::new(
SandboxConfiguration::MIN_INPUT_SIZE - 1,
SandboxConfiguration::MIN_OUTPUT_SIZE - 1,
None,
SandboxConfiguration::DEFAULT_SCRATCH_SIZE,
SandboxConfiguration::DEFAULT_INTERRUPT_RETRY_DELAY,
SandboxConfiguration::INTERRUPT_VCPU_SIGRTMIN_OFFSET,
#[cfg(gdb)]
None,
#[cfg(crashdump)]
true,
);
assert_eq!(SandboxConfiguration::MIN_INPUT_SIZE, cfg.input_data_size);
assert_eq!(SandboxConfiguration::MIN_OUTPUT_SIZE, cfg.output_data_size);
assert_eq!(0, cfg.heap_size_override);
cfg.set_input_data_size(SandboxConfiguration::MIN_INPUT_SIZE - 1);
cfg.set_output_data_size(SandboxConfiguration::MIN_OUTPUT_SIZE - 1);
assert_eq!(SandboxConfiguration::MIN_INPUT_SIZE, cfg.input_data_size);
assert_eq!(SandboxConfiguration::MIN_OUTPUT_SIZE, cfg.output_data_size);
}
mod proptests {
use proptest::prelude::*;
use super::SandboxConfiguration;
#[cfg(gdb)]
use crate::sandbox::config::DebugInfo;
proptest! {
#[test]
fn input_data_size(size in SandboxConfiguration::MIN_INPUT_SIZE..=SandboxConfiguration::MIN_INPUT_SIZE * 10) {
let mut cfg = SandboxConfiguration::default();
cfg.set_input_data_size(size);
prop_assert_eq!(size, cfg.get_input_data_size());
}
#[test]
fn output_data_size(size in SandboxConfiguration::MIN_OUTPUT_SIZE..=SandboxConfiguration::MIN_OUTPUT_SIZE * 10) {
let mut cfg = SandboxConfiguration::default();
cfg.set_output_data_size(size);
prop_assert_eq!(size, cfg.get_output_data_size());
}
#[test]
fn heap_size_override(size in 0x1000..=0x10000u64) {
let mut cfg = SandboxConfiguration::default();
cfg.set_heap_size(size);
prop_assert_eq!(size, cfg.heap_size_override);
}
#[test]
#[cfg(gdb)]
fn guest_debug_info(port in 9000..=u16::MAX) {
let mut cfg = SandboxConfiguration::default();
let debug_info = DebugInfo { port };
cfg.set_guest_debug_info(debug_info);
prop_assert_eq!(debug_info, *cfg.get_guest_debug_info().as_ref().unwrap());
}
}
}
}