#![allow(unsafe_code)]
use crate::error::{KernelError, Result};
#[cfg(feature = "std")]
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
pub const KVM_API_VERSION: u32 = 12;
pub const MAX_VCPUS: u32 = 254;
pub const MAX_MEMORY_SLOTS: u32 = 32;
#[allow(dead_code)]
mod kvm_ioctls {
pub const KVM_GET_API_VERSION: u64 = 0xAE00;
pub const KVM_CREATE_VM: u64 = 0xAE01;
pub const KVM_CHECK_EXTENSION: u64 = 0xAE03;
pub const KVM_GET_VCPU_MMAP_SIZE: u64 = 0xAE04;
pub const KVM_CREATE_VCPU: u64 = 0xAE41;
pub const KVM_SET_USER_MEMORY_REGION: u64 = 0x4020_AE46;
pub const KVM_RUN: u64 = 0xAE80;
pub const KVM_GET_REGS: u64 = 0x8090_AE81;
pub const KVM_SET_REGS: u64 = 0x4090_AE82;
pub const KVM_GET_SREGS: u64 = 0x8138_AE83;
pub const KVM_SET_SREGS: u64 = 0x4138_AE84;
pub const KVM_CAP_USER_MEMORY: u32 = 3;
pub const KVM_CAP_IRQCHIP: u32 = 0;
pub const KVM_CAP_HLT: u32 = 1;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum VmState {
#[default]
Created = 0,
Running = 1,
Paused = 2,
Stopped = 3,
Error = 4,
}
impl VmState {
#[must_use]
pub const fn can_start(&self) -> bool {
matches!(self, Self::Created | Self::Paused)
}
#[must_use]
pub const fn is_active(&self) -> bool {
matches!(self, Self::Running | Self::Paused)
}
}
#[derive(Debug, Clone)]
pub struct VmConfig {
pub vcpus: u32,
pub memory_mb: u64,
pub kernel_path: Option<String>,
pub kernel_cmdline: Option<String>,
pub initrd_path: Option<String>,
pub enable_kvm: bool,
pub vsock_path: Option<String>,
}
impl Default for VmConfig {
fn default() -> Self {
Self {
vcpus: 1,
memory_mb: 128,
kernel_path: None,
kernel_cmdline: None,
initrd_path: None,
enable_kvm: true,
vsock_path: None,
}
}
}
impl VmConfig {
#[must_use]
pub fn builder() -> VmConfigBuilder {
VmConfigBuilder::new()
}
pub fn validate(&self) -> Result<()> {
if self.vcpus == 0 || self.vcpus > MAX_VCPUS {
return Err(KernelError::InvalidArgument);
}
if self.memory_mb == 0 {
return Err(KernelError::InvalidArgument);
}
Ok(())
}
#[must_use]
pub const fn memory_bytes(&self) -> u64 {
self.memory_mb * 1024 * 1024
}
}
#[derive(Debug, Default)]
pub struct VmConfigBuilder {
config: VmConfig,
}
impl VmConfigBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub const fn vcpus(mut self, count: u32) -> Self {
self.config.vcpus = count;
self
}
#[must_use]
pub const fn memory_mb(mut self, mb: u64) -> Self {
self.config.memory_mb = mb;
self
}
#[must_use]
pub fn kernel_path(mut self, path: impl Into<String>) -> Self {
self.config.kernel_path = Some(path.into());
self
}
#[must_use]
pub fn kernel_cmdline(mut self, cmdline: impl Into<String>) -> Self {
self.config.kernel_cmdline = Some(cmdline.into());
self
}
#[must_use]
pub fn initrd_path(mut self, path: impl Into<String>) -> Self {
self.config.initrd_path = Some(path.into());
self
}
#[must_use]
pub const fn enable_kvm(mut self, enable: bool) -> Self {
self.config.enable_kvm = enable;
self
}
#[must_use]
pub fn vsock_path(mut self, path: impl Into<String>) -> Self {
self.config.vsock_path = Some(path.into());
self
}
pub fn build(self) -> Result<VmConfig> {
self.config.validate()?;
Ok(self.config)
}
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct VcpuRegs {
pub rax: u64,
pub rbx: u64,
pub rcx: u64,
pub rdx: u64,
pub rsi: u64,
pub rdi: u64,
pub rsp: u64,
pub rbp: u64,
pub r8: u64,
pub r9: u64,
pub r10: u64,
pub r11: u64,
pub r12: u64,
pub r13: u64,
pub r14: u64,
pub r15: u64,
pub rip: u64,
pub rflags: u64,
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct Segment {
pub base: u64,
pub limit: u32,
pub selector: u16,
pub type_: u8,
pub present: u8,
pub dpl: u8,
pub db: u8,
pub s: u8,
pub l: u8,
pub g: u8,
pub avl: u8,
pub unusable: u8,
pub padding: u8,
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct Dtable {
pub base: u64,
pub limit: u16,
pub padding: [u16; 3],
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct VcpuSregs {
pub cs: Segment,
pub ds: Segment,
pub es: Segment,
pub fs: Segment,
pub gs: Segment,
pub ss: Segment,
pub tr: Segment,
pub ldt: Segment,
pub gdt: Dtable,
pub idt: Dtable,
pub cr0: u64,
pub cr2: u64,
pub cr3: u64,
pub cr4: u64,
pub cr8: u64,
pub efer: u64,
pub apic_base: u64,
pub interrupt_bitmap: [u64; 4],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum ExitReason {
Unknown = 0,
Exception = 1,
ExternalInterrupt = 2,
TripleFault = 3,
Init = 4,
Sipi = 5,
Io = 6,
Halt = 7,
Cpuid = 8,
Hlt = 9,
Mmio = 10,
Hypercall = 11,
InternalError = 12,
Shutdown = 13,
SystemEvent = 14,
}
impl From<u32> for ExitReason {
fn from(value: u32) -> Self {
match value {
1 => Self::Exception,
2 => Self::ExternalInterrupt,
3 => Self::TripleFault,
4 => Self::Init,
5 => Self::Sipi,
6 => Self::Io,
7 => Self::Halt,
8 => Self::Cpuid,
9 => Self::Hlt,
10 => Self::Mmio,
11 => Self::Hypercall,
12 => Self::InternalError,
13 => Self::Shutdown,
14 => Self::SystemEvent,
_ => Self::Unknown,
}
}
}
#[derive(Debug, Clone)]
pub struct MemoryRegion {
pub slot: u32,
pub guest_phys_addr: u64,
pub memory_size: u64,
pub userspace_addr: u64,
pub flags: u32,
}
impl MemoryRegion {
#[must_use]
pub const fn new(slot: u32, guest_phys_addr: u64, memory_size: u64, userspace_addr: u64) -> Self {
Self {
slot,
guest_phys_addr,
memory_size,
userspace_addr,
flags: 0,
}
}
#[must_use]
pub const fn readonly(mut self) -> Self {
self.flags |= 1; self
}
}
#[cfg(feature = "std")]
pub struct Vcpu {
id: u32,
#[allow(dead_code)]
fd: i32,
running: AtomicBool,
}
#[cfg(feature = "std")]
impl Vcpu {
pub fn new(id: u32) -> Result<Self> {
Ok(Self {
id,
fd: -1, running: AtomicBool::new(false),
})
}
#[must_use]
pub const fn id(&self) -> u32 {
self.id
}
#[must_use]
pub fn is_running(&self) -> bool {
self.running.load(Ordering::Acquire)
}
pub fn get_regs(&self) -> Result<VcpuRegs> {
Ok(VcpuRegs::default())
}
pub fn set_regs(&self, _regs: &VcpuRegs) -> Result<()> {
Ok(())
}
pub fn get_sregs(&self) -> Result<VcpuSregs> {
Ok(VcpuSregs::default())
}
pub fn set_sregs(&self, _sregs: &VcpuSregs) -> Result<()> {
Ok(())
}
pub fn run(&self) -> Result<ExitReason> {
self.running.store(true, Ordering::Release);
self.running.store(false, Ordering::Release);
Ok(ExitReason::Halt)
}
}
#[cfg(feature = "std")]
impl std::fmt::Debug for Vcpu {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Vcpu")
.field("id", &self.id)
.field("running", &self.is_running())
.finish()
}
}
#[cfg(feature = "std")]
pub struct MicroVm {
config: VmConfig,
state: std::sync::RwLock<VmState>,
vcpus: Vec<Vcpu>,
memory_size: u64,
regions: Vec<MemoryRegion>,
exit_count: AtomicU64,
}
#[cfg(feature = "std")]
impl MicroVm {
pub fn create(config: VmConfig) -> Result<Self> {
config.validate()?;
let mut vcpus = Vec::with_capacity(config.vcpus as usize);
for i in 0..config.vcpus {
vcpus.push(Vcpu::new(i)?);
}
Ok(Self {
memory_size: config.memory_bytes(),
config,
state: std::sync::RwLock::new(VmState::Created),
vcpus,
regions: Vec::new(),
exit_count: AtomicU64::new(0),
})
}
pub fn state(&self) -> VmState {
*self.state.read().unwrap()
}
#[must_use]
pub const fn config(&self) -> &VmConfig {
&self.config
}
#[must_use]
pub fn vcpu_count(&self) -> usize {
self.vcpus.len()
}
#[must_use]
pub const fn memory_size(&self) -> u64 {
self.memory_size
}
pub fn add_memory_region(&mut self, region: MemoryRegion) -> Result<()> {
if self.regions.len() >= MAX_MEMORY_SLOTS as usize {
return Err(KernelError::OutOfMemory);
}
self.regions.push(region);
Ok(())
}
pub fn run(&self) -> Result<ExitReason> {
{
let mut state = self.state.write().map_err(|_| KernelError::ResourceBusy)?;
if !state.can_start() {
return Err(KernelError::InvalidRequest);
}
*state = VmState::Running;
}
let exit = if let Some(vcpu) = self.vcpus.first() {
vcpu.run()?
} else {
ExitReason::Halt
};
self.exit_count.fetch_add(1, Ordering::Relaxed);
{
let mut state = self.state.write().map_err(|_| KernelError::ResourceBusy)?;
*state = VmState::Stopped;
}
Ok(exit)
}
pub fn pause(&self) -> Result<()> {
let mut state = self.state.write().map_err(|_| KernelError::ResourceBusy)?;
if *state != VmState::Running {
return Err(KernelError::InvalidRequest);
}
*state = VmState::Paused;
Ok(())
}
pub fn resume(&self) -> Result<()> {
let mut state = self.state.write().map_err(|_| KernelError::ResourceBusy)?;
if *state != VmState::Paused {
return Err(KernelError::InvalidRequest);
}
*state = VmState::Running;
Ok(())
}
pub fn stop(&self) -> Result<()> {
let mut state = self.state.write().map_err(|_| KernelError::ResourceBusy)?;
*state = VmState::Stopped;
Ok(())
}
#[must_use]
pub fn exit_count(&self) -> u64 {
self.exit_count.load(Ordering::Relaxed)
}
}
#[cfg(feature = "std")]
impl std::fmt::Debug for MicroVm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MicroVm")
.field("vcpus", &self.vcpu_count())
.field("memory_mb", &self.config.memory_mb)
.field("state", &self.state())
.finish()
}
}
#[derive(Debug, Clone, Default)]
pub struct JailerConfig {
pub chroot_dir: Option<String>,
pub uid: Option<u32>,
pub gid: Option<u32>,
pub seccomp: bool,
pub netns: bool,
pub cpuset: Option<String>,
pub memory_limit: Option<u64>,
}
impl JailerConfig {
#[must_use]
pub fn minimal() -> Self {
Self {
chroot_dir: Some("/srv/jailer".to_string()),
uid: Some(65534), gid: Some(65534), seccomp: true,
netns: true,
cpuset: None,
memory_limit: Some(256 * 1024 * 1024), }
}
#[must_use]
pub fn production(chroot: impl Into<String>) -> Self {
Self {
chroot_dir: Some(chroot.into()),
uid: Some(65534),
gid: Some(65534),
seccomp: true,
netns: true,
cpuset: None,
memory_limit: None,
}
}
}
#[cfg(feature = "std")]
pub struct Jailer {
config: JailerConfig,
active: AtomicBool,
}
#[cfg(feature = "std")]
impl Jailer {
#[must_use]
pub fn new(config: JailerConfig) -> Self {
Self {
config,
active: AtomicBool::new(false),
}
}
#[must_use]
pub const fn config(&self) -> &JailerConfig {
&self.config
}
#[must_use]
pub fn is_active(&self) -> bool {
self.active.load(Ordering::Acquire)
}
pub fn enter(&self) -> Result<()> {
self.active.store(true, Ordering::Release);
Ok(())
}
pub fn exit(&self) -> Result<()> {
self.active.store(false, Ordering::Release);
Ok(())
}
}
#[cfg(feature = "std")]
impl std::fmt::Debug for Jailer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Jailer")
.field("active", &self.is_active())
.field("seccomp", &self.config.seccomp)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vm_state_default() {
assert_eq!(VmState::default(), VmState::Created);
}
#[test]
fn test_vm_state_can_start() {
assert!(VmState::Created.can_start());
assert!(VmState::Paused.can_start());
assert!(!VmState::Running.can_start());
assert!(!VmState::Stopped.can_start());
}
#[test]
fn test_vm_state_is_active() {
assert!(VmState::Running.is_active());
assert!(VmState::Paused.is_active());
assert!(!VmState::Created.is_active());
assert!(!VmState::Stopped.is_active());
}
#[test]
fn test_config_default() {
let config = VmConfig::default();
assert_eq!(config.vcpus, 1);
assert_eq!(config.memory_mb, 128);
assert!(config.enable_kvm);
}
#[test]
fn test_config_builder() {
let config = VmConfig::builder()
.vcpus(4)
.memory_mb(512)
.kernel_path("/boot/vmlinux")
.kernel_cmdline("console=ttyS0")
.build()
.unwrap();
assert_eq!(config.vcpus, 4);
assert_eq!(config.memory_mb, 512);
assert_eq!(config.kernel_path, Some("/boot/vmlinux".to_string()));
}
#[test]
fn test_config_validate_zero_vcpus() {
let config = VmConfig::builder().vcpus(0).memory_mb(128);
assert!(config.build().is_err());
}
#[test]
fn test_config_validate_too_many_vcpus() {
let config = VmConfig::builder().vcpus(255).memory_mb(128);
assert!(config.build().is_err());
}
#[test]
fn test_config_memory_bytes() {
let config = VmConfig::builder().memory_mb(256).build().unwrap();
assert_eq!(config.memory_bytes(), 256 * 1024 * 1024);
}
#[test]
fn test_vcpu_regs_default() {
let regs = VcpuRegs::default();
assert_eq!(regs.rax, 0);
assert_eq!(regs.rip, 0);
}
#[test]
fn test_exit_reason_from() {
assert_eq!(ExitReason::from(6), ExitReason::Io);
assert_eq!(ExitReason::from(9), ExitReason::Hlt);
assert_eq!(ExitReason::from(999), ExitReason::Unknown);
}
#[test]
fn test_memory_region_new() {
let region = MemoryRegion::new(0, 0x1000, 4096, 0x7f0000);
assert_eq!(region.slot, 0);
assert_eq!(region.guest_phys_addr, 0x1000);
assert_eq!(region.memory_size, 4096);
assert_eq!(region.flags, 0);
}
#[test]
fn test_memory_region_readonly() {
let region = MemoryRegion::new(0, 0, 4096, 0).readonly();
assert_eq!(region.flags, 1);
}
#[test]
fn test_vcpu_new() {
let vcpu = Vcpu::new(0).unwrap();
assert_eq!(vcpu.id(), 0);
assert!(!vcpu.is_running());
}
#[test]
fn test_vcpu_get_set_regs() {
let vcpu = Vcpu::new(0).unwrap();
let regs = vcpu.get_regs().unwrap();
vcpu.set_regs(®s).unwrap();
}
#[test]
fn test_vcpu_run() {
let vcpu = Vcpu::new(0).unwrap();
let exit = vcpu.run().unwrap();
assert_eq!(exit, ExitReason::Halt);
}
#[test]
fn test_microvm_create() {
let config = VmConfig::builder()
.vcpus(2)
.memory_mb(256)
.build()
.unwrap();
let vm = MicroVm::create(config).unwrap();
assert_eq!(vm.vcpu_count(), 2);
assert_eq!(vm.memory_size(), 256 * 1024 * 1024);
assert_eq!(vm.state(), VmState::Created);
}
#[test]
fn test_microvm_run() {
let config = VmConfig::builder().build().unwrap();
let vm = MicroVm::create(config).unwrap();
let exit = vm.run().unwrap();
assert_eq!(exit, ExitReason::Halt);
assert_eq!(vm.state(), VmState::Stopped);
assert_eq!(vm.exit_count(), 1);
}
#[test]
fn test_microvm_add_memory_region() {
let config = VmConfig::builder().build().unwrap();
let mut vm = MicroVm::create(config).unwrap();
let region = MemoryRegion::new(0, 0, 4096, 0x7f0000);
vm.add_memory_region(region).unwrap();
}
#[test]
fn test_microvm_pause_resume() {
let config = VmConfig::builder().build().unwrap();
let vm = MicroVm::create(config).unwrap();
assert!(vm.pause().is_err());
vm.run().unwrap();
assert_eq!(vm.state(), VmState::Stopped);
}
#[test]
fn test_microvm_debug() {
let config = VmConfig::builder().vcpus(2).memory_mb(128).build().unwrap();
let vm = MicroVm::create(config).unwrap();
let debug = format!("{:?}", vm);
assert!(debug.contains("MicroVm"));
assert!(debug.contains("vcpus"));
}
#[test]
fn test_jailer_config_default() {
let config = JailerConfig::default();
assert!(config.chroot_dir.is_none());
assert!(!config.seccomp);
}
#[test]
fn test_jailer_config_minimal() {
let config = JailerConfig::minimal();
assert!(config.chroot_dir.is_some());
assert!(config.seccomp);
assert!(config.netns);
}
#[test]
fn test_jailer_config_production() {
let config = JailerConfig::production("/var/lib/jailer");
assert_eq!(config.chroot_dir, Some("/var/lib/jailer".to_string()));
assert!(config.seccomp);
}
#[test]
fn test_jailer_new() {
let config = JailerConfig::minimal();
let jailer = Jailer::new(config);
assert!(!jailer.is_active());
}
#[test]
fn test_jailer_enter_exit() {
let config = JailerConfig::minimal();
let jailer = Jailer::new(config);
jailer.enter().unwrap();
assert!(jailer.is_active());
jailer.exit().unwrap();
assert!(!jailer.is_active());
}
#[test]
fn test_jailer_debug() {
let config = JailerConfig::minimal();
let jailer = Jailer::new(config);
let debug = format!("{:?}", jailer);
assert!(debug.contains("Jailer"));
assert!(debug.contains("seccomp"));
}
}