use log::LevelFilter;
use tracing::{instrument, Span};
use crate::error::HyperlightError::ExecutionCanceledByHost;
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
use crate::metrics::METRIC_GUEST_CANCELLATION;
use crate::{log_then_return, new_error, HyperlightError, Result};
#[cfg(any(kvm, mshv, target_os = "windows"))]
pub mod fpu;
pub mod handlers;
#[cfg(mshv)]
pub mod hyperv_linux;
#[cfg(target_os = "windows")]
pub(crate) mod hyperv_windows;
pub(crate) mod hypervisor_handler;
#[cfg(gdb)]
mod gdb;
#[cfg(inprocess)]
pub mod inprocess;
#[cfg(kvm)]
pub mod kvm;
#[cfg(target_os = "windows")]
pub(crate) mod surrogate_process;
#[cfg(target_os = "windows")]
pub(crate) mod surrogate_process_manager;
#[cfg(target_os = "windows")]
pub(crate) mod windows_hypervisor_platform;
#[cfg(target_os = "windows")]
pub(crate) mod wrappers;
#[cfg(crashdump)]
pub(crate) mod crashdump;
use std::fmt::Debug;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
#[cfg(gdb)]
use gdb::VcpuStopReason;
#[cfg(gdb)]
use self::handlers::{DbgMemAccessHandlerCaller, DbgMemAccessHandlerWrapper};
use self::handlers::{
MemAccessHandlerCaller, MemAccessHandlerWrapper, OutBHandlerCaller, OutBHandlerWrapper,
};
use crate::hypervisor::hypervisor_handler::HypervisorHandler;
use crate::mem::ptr::RawPtr;
pub(crate) const CR4_PAE: u64 = 1 << 5;
pub(crate) const CR4_OSFXSR: u64 = 1 << 9;
pub(crate) const CR4_OSXMMEXCPT: u64 = 1 << 10;
pub(crate) const CR0_PE: u64 = 1;
pub(crate) const CR0_MP: u64 = 1 << 1;
pub(crate) const CR0_ET: u64 = 1 << 4;
pub(crate) const CR0_NE: u64 = 1 << 5;
pub(crate) const CR0_WP: u64 = 1 << 16;
pub(crate) const CR0_AM: u64 = 1 << 18;
pub(crate) const CR0_PG: u64 = 1 << 31;
pub(crate) const EFER_LME: u64 = 1 << 8;
pub(crate) const EFER_LMA: u64 = 1 << 10;
pub(crate) const EFER_SCE: u64 = 1;
pub(crate) const EFER_NX: u64 = 1 << 11;
pub enum HyperlightExit {
#[cfg(gdb)]
Debug(VcpuStopReason),
Halt(),
IoOut(u16, Vec<u8>, u64, u64),
Mmio(u64),
AccessViolation(u64, MemoryRegionFlags, MemoryRegionFlags),
Cancelled(),
Unknown(String),
Retry(),
}
pub(crate) trait Hypervisor: Debug + Sync + Send {
#[allow(clippy::too_many_arguments)]
fn initialise(
&mut self,
peb_addr: RawPtr,
seed: u64,
page_size: u32,
outb_handle_fn: OutBHandlerWrapper,
mem_access_fn: MemAccessHandlerWrapper,
hv_handler: Option<HypervisorHandler>,
guest_max_log_level: Option<LevelFilter>,
#[cfg(gdb)] dbg_mem_access_fn: DbgMemAccessHandlerWrapper,
) -> Result<()>;
fn dispatch_call_from_host(
&mut self,
dispatch_func_addr: RawPtr,
outb_handle_fn: OutBHandlerWrapper,
mem_access_fn: MemAccessHandlerWrapper,
hv_handler: Option<HypervisorHandler>,
#[cfg(gdb)] dbg_mem_access_fn: DbgMemAccessHandlerWrapper,
) -> Result<()>;
fn handle_io(
&mut self,
port: u16,
data: Vec<u8>,
rip: u64,
instruction_length: u64,
outb_handle_fn: OutBHandlerWrapper,
) -> Result<()>;
fn run(&mut self) -> Result<HyperlightExit>;
fn get_memory_access_violation(
&self,
gpa: usize,
mem_regions: &[MemoryRegion],
access_info: MemoryRegionFlags,
) -> Option<HyperlightExit> {
let region = mem_regions
.iter()
.find(|region| region.guest_region.contains(&gpa));
if let Some(region) = region {
if !region.flags.contains(access_info)
|| region.flags.contains(MemoryRegionFlags::STACK_GUARD)
{
return Some(HyperlightExit::AccessViolation(
gpa as u64,
access_info,
region.flags,
));
}
}
None
}
fn get_max_log_level(&self) -> u32 {
let val = std::env::var("RUST_LOG").unwrap_or_default();
let level = if val.contains("hyperlight_guest") {
val.split(',')
.find(|s| s.contains("hyperlight_guest"))
.unwrap_or("")
.split('=')
.nth(1)
.unwrap_or("")
} else if val.contains("hyperlight_host") {
val.split(',')
.find(|s| s.contains("hyperlight_host"))
.unwrap_or("")
.split('=')
.nth(1)
.unwrap_or("")
} else {
val.split(',').find(|s| !s.contains("=")).unwrap_or("")
};
log::info!("Determined guest log level: {}", level);
LevelFilter::from_str(level).unwrap_or(LevelFilter::Error) as u32
}
fn as_mut_hypervisor(&mut self) -> &mut dyn Hypervisor;
#[cfg(target_os = "windows")]
fn get_partition_handle(&self) -> windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE;
#[cfg(crashdump)]
fn get_memory_regions(&self) -> &[MemoryRegion];
#[cfg(gdb)]
fn handle_debug(
&mut self,
_dbg_mem_access_fn: Arc<Mutex<dyn DbgMemAccessHandlerCaller>>,
_stop_reason: VcpuStopReason,
) -> Result<()> {
unimplemented!()
}
}
pub struct VirtualCPU {}
impl VirtualCPU {
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
pub fn run(
hv: &mut dyn Hypervisor,
hv_handler: Option<HypervisorHandler>,
outb_handle_fn: Arc<Mutex<dyn OutBHandlerCaller>>,
mem_access_fn: Arc<Mutex<dyn MemAccessHandlerCaller>>,
#[cfg(gdb)] dbg_mem_access_fn: Arc<Mutex<dyn DbgMemAccessHandlerCaller>>,
) -> Result<()> {
loop {
match hv.run() {
#[cfg(gdb)]
Ok(HyperlightExit::Debug(stop_reason)) => {
if let Err(e) = hv.handle_debug(dbg_mem_access_fn.clone(), stop_reason) {
log_then_return!(e);
}
}
Ok(HyperlightExit::Halt()) => {
break;
}
Ok(HyperlightExit::IoOut(port, data, rip, instruction_length)) => {
hv.handle_io(port, data, rip, instruction_length, outb_handle_fn.clone())?
}
Ok(HyperlightExit::Mmio(addr)) => {
#[cfg(crashdump)]
crashdump::crashdump_to_tempfile(hv)?;
mem_access_fn
.clone()
.try_lock()
.map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
.call()?;
log_then_return!("MMIO access address {:#x}", addr);
}
Ok(HyperlightExit::AccessViolation(addr, tried, region_permission)) => {
#[cfg(crashdump)]
crashdump::crashdump_to_tempfile(hv)?;
if region_permission.intersects(MemoryRegionFlags::STACK_GUARD) {
return Err(HyperlightError::StackOverflow());
}
log_then_return!(HyperlightError::MemoryAccessViolation(
addr,
tried,
region_permission
));
}
Ok(HyperlightExit::Cancelled()) => {
if let Some(hvh) = hv_handler {
hvh.set_running(false);
#[cfg(target_os = "linux")]
hvh.set_run_cancelled(true);
}
metrics::counter!(METRIC_GUEST_CANCELLATION).increment(1);
log_then_return!(ExecutionCanceledByHost());
}
Ok(HyperlightExit::Unknown(reason)) => {
#[cfg(crashdump)]
crashdump::crashdump_to_tempfile(hv)?;
log_then_return!("Unexpected VM Exit {:?}", reason);
}
Ok(HyperlightExit::Retry()) => continue,
Err(e) => {
#[cfg(crashdump)]
crashdump::crashdump_to_tempfile(hv)?;
return Err(e);
}
}
}
Ok(())
}
}
#[cfg(all(test, any(target_os = "windows", kvm)))]
pub(crate) mod tests {
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use hyperlight_testing::dummy_guest_as_string;
#[cfg(gdb)]
use super::handlers::DbgMemAccessHandlerWrapper;
use super::handlers::{MemAccessHandlerWrapper, OutBHandlerWrapper};
use crate::hypervisor::hypervisor_handler::{
HvHandlerConfig, HypervisorHandler, HypervisorHandlerAction,
};
use crate::mem::ptr::RawPtr;
use crate::sandbox::uninitialized::GuestBinary;
use crate::sandbox::{SandboxConfiguration, UninitializedSandbox};
use crate::{new_error, Result};
pub(crate) fn test_initialise(
outb_hdl: OutBHandlerWrapper,
mem_access_hdl: MemAccessHandlerWrapper,
#[cfg(gdb)] dbg_mem_access_fn: DbgMemAccessHandlerWrapper,
) -> Result<()> {
let filename = dummy_guest_as_string().map_err(|e| new_error!("{}", e))?;
if !Path::new(&filename).exists() {
return Err(new_error!(
"test_initialise: file {} does not exist",
filename
));
}
let sandbox =
UninitializedSandbox::new(GuestBinary::FilePath(filename.clone()), None, None, None)?;
let (hshm, gshm) = sandbox.mgr.build();
drop(hshm);
let hv_handler_config = HvHandlerConfig {
outb_handler: outb_hdl,
mem_access_handler: mem_access_hdl,
#[cfg(gdb)]
dbg_mem_access_handler: dbg_mem_access_fn,
seed: 1234567890,
page_size: 4096,
peb_addr: RawPtr::from(0x230000),
dispatch_function_addr: Arc::new(Mutex::new(None)),
max_init_time: Duration::from_millis(
SandboxConfiguration::DEFAULT_MAX_INITIALIZATION_TIME as u64,
),
max_exec_time: Duration::from_millis(
SandboxConfiguration::DEFAULT_MAX_EXECUTION_TIME as u64,
),
max_wait_for_cancellation: Duration::from_millis(
SandboxConfiguration::DEFAULT_MAX_WAIT_FOR_CANCELLATION as u64,
),
max_guest_log_level: None,
};
let mut hv_handler = HypervisorHandler::new(hv_handler_config);
hv_handler.start_hypervisor_handler(
gshm,
#[cfg(gdb)]
None,
)?;
hv_handler.execute_hypervisor_handler_action(HypervisorHandlerAction::Initialise)
}
}