diff --git a/Cargo.lock b/Cargo.lock index ac3299866..da87b0314 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1810,8 +1810,7 @@ dependencies = [ [[package]] name = "kvm-bindings" version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b3c06ff73c7ce03e780887ec2389d62d2a2a9ddf471ab05c2ff69207cd3f3b4" +source = "git+https://github.com/rust-vmm/kvm?rev=3ffc9b62af5978553f73cc0ec79fad13fdd47146#3ffc9b62af5978553f73cc0ec79fad13fdd47146" dependencies = [ "vmm-sys-util", ] @@ -1819,8 +1818,7 @@ dependencies = [ [[package]] name = "kvm-ioctls" version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "333f77a20344a448f3f70664918135fddeb804e938f28a99d685bd92926e0b19" +source = "git+https://github.com/rust-vmm/kvm?rev=3ffc9b62af5978553f73cc0ec79fad13fdd47146#3ffc9b62af5978553f73cc0ec79fad13fdd47146" dependencies = [ "bitflags 2.10.0", "kvm-bindings", diff --git a/src/hyperlight_host/Cargo.toml b/src/hyperlight_host/Cargo.toml index cbfbe1377..1278dfbb4 100644 --- a/src/hyperlight_host/Cargo.toml +++ b/src/hyperlight_host/Cargo.toml @@ -75,8 +75,8 @@ windows-version = "0.1" lazy_static = "1.4.0" [target.'cfg(unix)'.dependencies] -kvm-bindings = { version = "0.14", features = ["fam-wrappers"], optional = true } -kvm-ioctls = { version = "0.24", optional = true } +kvm-bindings = { git = "https://github.com/rust-vmm/kvm", rev = "3ffc9b62af5978553f73cc0ec79fad13fdd47146", features = ["fam-wrappers"], optional = true } +kvm-ioctls = { git = "https://github.com/rust-vmm/kvm", rev = "3ffc9b62af5978553f73cc0ec79fad13fdd47146", optional = true } mshv-bindings = { version = "0.6", optional = true } mshv-ioctls = { version = "0.6", optional = true} diff --git a/src/hyperlight_host/src/error.rs b/src/hyperlight_host/src/error.rs index 1bca9e944..733f36776 100644 --- a/src/hyperlight_host/src/error.rs +++ b/src/hyperlight_host/src/error.rs @@ -144,6 +144,14 @@ pub enum HyperlightError { #[error("Memory Access Violation at address {0:#x} of type {1}, but memory is marked as {2}")] MemoryAccessViolation(u64, MemoryRegionFlags, MemoryRegionFlags), + /// MSR Read Violation. Guest attempted to read from a Model-Specific Register + #[error("Guest attempted to read from MSR {0:#x}")] + MsrReadViolation(u32), + + /// MSR Write Violation. Guest attempted to write to a Model-Specific Register + #[error("Guest attempted to write {1:#x} to MSR {0:#x}")] + MsrWriteViolation(u32, u64), + /// Memory Allocation Failed. #[error("Memory Allocation Failed with OS Error {0:?}.")] MemoryAllocationFailed(Option), @@ -325,6 +333,8 @@ impl HyperlightError { | HyperlightError::ExecutionAccessViolation(_) | HyperlightError::StackOverflow() | HyperlightError::MemoryAccessViolation(_, _, _) + | HyperlightError::MsrReadViolation(_) + | HyperlightError::MsrWriteViolation(_, _) | HyperlightError::SnapshotSizeMismatch(_, _) | HyperlightError::MemoryRegionSizeMismatch(_, _, _) // HyperlightVmError::Restore is already handled manually in restore(), but we mark it diff --git a/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs b/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs index 7cf1f5ea0..0872c13ec 100644 --- a/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs +++ b/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs @@ -155,6 +155,14 @@ impl DispatchGuestCallError { region_flags, }) => HyperlightError::MemoryAccessViolation(addr, access_type, region_flags), + DispatchGuestCallError::Run(RunVmError::MsrReadViolation(msr_index)) => { + HyperlightError::MsrReadViolation(msr_index) + } + + DispatchGuestCallError::Run(RunVmError::MsrWriteViolation { msr_index, value }) => { + HyperlightError::MsrWriteViolation(msr_index, value) + } + // Leave others as is other => HyperlightVmError::DispatchGuestCall(other).into(), }; @@ -203,6 +211,10 @@ pub enum RunVmError { MmioReadUnmapped(u64), #[error("MMIO WRITE access to unmapped address {0:#x}")] MmioWriteUnmapped(u64), + #[error("Guest attempted to read from MSR {0:#x}")] + MsrReadViolation(u32), + #[error("Guest attempted to write {value:#x} to MSR {msr_index:#x}")] + MsrWriteViolation { msr_index: u32, value: u64 }, #[error("vCPU run failed: {0}")] RunVcpu(#[from] RunVcpuError), #[error("Unexpected VM exit: {0}")] @@ -340,7 +352,7 @@ impl HyperlightVm { _pml4_addr: u64, entrypoint: Option, rsp_gva: u64, - #[cfg_attr(target_os = "windows", allow(unused_variables))] config: &SandboxConfiguration, + config: &SandboxConfiguration, #[cfg(gdb)] gdb_conn: Option>, #[cfg(crashdump)] rt_cfg: SandboxRuntimeConfig, #[cfg(feature = "mem_profile")] trace_info: MemTraceInfo, @@ -350,7 +362,7 @@ impl HyperlightVm { #[cfg(not(gdb))] type VmType = Box; - let vm: VmType = match get_available_hypervisor() { + let mut vm: VmType = match get_available_hypervisor() { #[cfg(kvm)] Some(HypervisorType::Kvm) => Box::new(KvmVm::new().map_err(VmError::CreateVm)?), #[cfg(mshv3)] @@ -360,6 +372,11 @@ impl HyperlightVm { None => return Err(CreateHyperlightVmError::NoHypervisorFound), }; + // Enable MSR intercepts unless the user explicitly allows MSR access + if !config.get_allow_msr() { + vm.enable_msr_intercept().map_err(VmError::CreateVm)?; + } + #[cfg(feature = "init-paging")] vm.set_sregs(&CommonSpecialRegisters::standard_64bit_defaults(_pml4_addr)) .map_err(VmError::Register)?; @@ -811,6 +828,12 @@ impl HyperlightVm { } } } + Ok(VmExit::MsrRead(msr_index)) => { + break Err(RunVmError::MsrReadViolation(msr_index)); + } + Ok(VmExit::MsrWrite { msr_index, value }) => { + break Err(RunVmError::MsrWriteViolation { msr_index, value }); + } Ok(VmExit::Cancelled()) => { // If cancellation was not requested for this specific guest function call, // the vcpu was interrupted by a stale cancellation. This can occur when: @@ -940,6 +963,7 @@ impl HyperlightVm { .set_sregs(&CommonSpecialRegisters::standard_real_mode_defaults())?; } + // MSRs are not reset because we disallow read/write of MSRs, unless explicitly unsafely allowed by the user. Ok(()) } diff --git a/src/hyperlight_host/src/hypervisor/virtual_machine/kvm.rs b/src/hyperlight_host/src/hypervisor/virtual_machine/kvm.rs index ecf3b6b91..e61ddc557 100644 --- a/src/hyperlight_host/src/hypervisor/virtual_machine/kvm.rs +++ b/src/hyperlight_host/src/hypervisor/virtual_machine/kvm.rs @@ -19,10 +19,14 @@ use std::sync::LazyLock; #[cfg(gdb)] use kvm_bindings::kvm_guest_debug; use kvm_bindings::{ - kvm_debugregs, kvm_fpu, kvm_regs, kvm_sregs, kvm_userspace_memory_region, kvm_xsave, + kvm_debugregs, kvm_enable_cap, kvm_fpu, kvm_regs, kvm_sregs, kvm_userspace_memory_region, + kvm_xsave, }; use kvm_ioctls::Cap::UserMemory; -use kvm_ioctls::{Kvm, VcpuExit, VcpuFd, VmFd}; +use kvm_ioctls::{ + Cap, Kvm, MsrExitReason, MsrFilterDefaultAction, MsrFilterRange, MsrFilterRangeFlags, VcpuExit, + VcpuFd, VmFd, +}; use tracing::{Span, instrument}; #[cfg(feature = "trace_guest")] use tracing_opentelemetry::OpenTelemetrySpanExt; @@ -139,6 +143,36 @@ impl KvmVm { } impl VirtualMachine for KvmVm { + fn enable_msr_intercept(&mut self) -> std::result::Result<(), CreateVmError> { + let cap = kvm_enable_cap { + cap: Cap::X86UserSpaceMsr as u32, + args: [MsrExitReason::Filter.bits() as u64, 0, 0, 0], + ..Default::default() + }; + self.vm_fd + .enable_cap(&cap) + .map_err(|e| CreateVmError::EnableMsrIntercept(e.into()))?; + + // Install a deny-all MSR filter (KVM_X86_SET_MSR_FILTER). + // At least one range is required when using KVM_MSR_FILTER_DEFAULT_DENY; + // from the docs: "Calling this ioctl with an empty set of ranges + // (all nmsrs == 0) disables MSR filtering. In that mode, + // KVM_MSR_FILTER_DEFAULT_DENY is invalid and causes an error." + let bitmap = [0u8; 1]; // 1 byte covers 8 MSRs, all bits 0 (deny) + self.vm_fd + .set_msr_filter( + MsrFilterDefaultAction::DENY, + &[MsrFilterRange { + flags: MsrFilterRangeFlags::READ | MsrFilterRangeFlags::WRITE, + base: 0, + msr_count: 1, + bitmap: &bitmap, + }], + ) + .map_err(|e| CreateVmError::EnableMsrIntercept(e.into()))?; + Ok(()) + } + unsafe fn map_memory( &mut self, (slot, region): (u32, &MemoryRegion), @@ -176,6 +210,40 @@ impl VirtualMachine for KvmVm { Ok(VcpuExit::IoOut(port, data)) => Ok(VmExit::IoOut(port, data.to_vec())), Ok(VcpuExit::MmioRead(addr, _)) => Ok(VmExit::MmioRead(addr)), Ok(VcpuExit::MmioWrite(addr, _)) => Ok(VmExit::MmioWrite(addr)), + // KVM_EXIT_X86_RDMSR / KVM_EXIT_X86_WRMSR (KVM API ยง5, kvm_run structure): + // + // The "index" field tells userspace which MSR the guest wants to + // read/write. If the request was unsuccessful, userspace indicates + // that with a "1" in the "error" field. "This will inject a #GP + // into the guest when the VCPU is executed again." + // + // "for KVM_EXIT_IO, KVM_EXIT_MMIO, [...] KVM_EXIT_X86_RDMSR and + // KVM_EXIT_X86_WRMSR the corresponding operations are complete + // (and guest state is consistent) only after userspace has + // re-entered the kernel with KVM_RUN." + // + // We set error=1 and then re-run with `immediate_exit` to let KVM + // inject the #GP without executing further guest code. From the + // kvm_run docs: "[immediate_exit] is polled once when KVM_RUN + // starts; if non-zero, KVM_RUN exits immediately, returning + // -EINTR." + Ok(VcpuExit::X86Rdmsr(msr_exit)) => { + let msr_index = msr_exit.index; + *msr_exit.error = 1; + self.vcpu_fd.set_kvm_immediate_exit(1); + let _ = self.vcpu_fd.run(); + self.vcpu_fd.set_kvm_immediate_exit(0); + Ok(VmExit::MsrRead(msr_index)) + } + Ok(VcpuExit::X86Wrmsr(msr_exit)) => { + let msr_index = msr_exit.index; + let value = msr_exit.data; + *msr_exit.error = 1; + self.vcpu_fd.set_kvm_immediate_exit(1); + let _ = self.vcpu_fd.run(); + self.vcpu_fd.set_kvm_immediate_exit(0); + Ok(VmExit::MsrWrite { msr_index, value }) + } #[cfg(gdb)] Ok(VcpuExit::Debug(debug_exit)) => Ok(VmExit::Debug { dr6: debug_exit.dr6, diff --git a/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs b/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs index 82e05c104..3f5e2f648 100644 --- a/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs +++ b/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs @@ -130,6 +130,10 @@ pub(crate) enum VmExit { MmioRead(u64), /// The vCPU tried to write to the given (unmapped) addr MmioWrite(u64), + /// The vCPU tried to read from the given MSR + MsrRead(u32), + /// The vCPU tried to write to the given MSR with the given value + MsrWrite { msr_index: u32, value: u64 }, /// The vCPU execution has been cancelled Cancelled(), /// The vCPU has exited for a reason that is not handled by Hyperlight @@ -170,6 +174,8 @@ pub enum CreateVmError { CreateVcpuFd(HypervisorError), #[error("VM creation failed: {0}")] CreateVmFd(HypervisorError), + #[error("Failed to enable MSR intercept: {0}")] + EnableMsrIntercept(HypervisorError), #[error("Hypervisor is not available: {0}")] HypervisorNotAvailable(HypervisorError), #[error("Initialize VM failed: {0}")] @@ -184,8 +190,10 @@ pub enum CreateVmError { /// RunVCPU error #[derive(Debug, Clone, thiserror::Error)] pub enum RunVcpuError { - #[error("Failed to decode message type: {0}")] + #[error("Failed to decode IO message type: {0}")] DecodeIOMessage(u32), + #[error("Failed to decode MSR message type: {0}")] + DecodeMsrMessage(u32), #[cfg(gdb)] #[error("Failed to get DR6 debug register: {0}")] GetDr6(HypervisorError), @@ -344,6 +352,10 @@ pub(crate) trait VirtualMachine: Debug + Send { #[cfg(feature = "init-paging")] fn set_xsave(&self, xsave: &[u32]) -> std::result::Result<(), RegisterError>; + /// Enable MSR intercepts for this VM. When enabled, all MSR reads and + /// writes by the guest will cause a VM exit instead of being executed. + fn enable_msr_intercept(&mut self) -> std::result::Result<(), CreateVmError>; + /// Get partition handle #[cfg(target_os = "windows")] fn partition_handle(&self) -> windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE; diff --git a/src/hyperlight_host/src/hypervisor/virtual_machine/mshv.rs b/src/hyperlight_host/src/hypervisor/virtual_machine/mshv.rs index 74d7834ee..abd5d6c63 100644 --- a/src/hyperlight_host/src/hypervisor/virtual_machine/mshv.rs +++ b/src/hyperlight_host/src/hypervisor/virtual_machine/mshv.rs @@ -21,12 +21,16 @@ use std::sync::LazyLock; #[cfg(gdb)] use mshv_bindings::{DebugRegisters, hv_message_type_HVMSG_X64_EXCEPTION_INTERCEPT}; use mshv_bindings::{ - FloatingPointUnit, SpecialRegisters, StandardRegisters, XSave, hv_message_type, + FloatingPointUnit, HV_INTERCEPT_ACCESS_MASK_READ, HV_INTERCEPT_ACCESS_MASK_WRITE, + HV_INTERCEPT_ACCESS_READ, HV_INTERCEPT_ACCESS_WRITE, SpecialRegisters, StandardRegisters, + XSave, hv_intercept_type_HV_INTERCEPT_TYPE_X64_MSR, hv_message_type, hv_message_type_HVMSG_GPA_INTERCEPT, hv_message_type_HVMSG_UNMAPPED_GPA, hv_message_type_HVMSG_X64_HALT, hv_message_type_HVMSG_X64_IO_PORT_INTERCEPT, + hv_message_type_HVMSG_X64_MSR_INTERCEPT, hv_partition_property_code_HV_PARTITION_PROPERTY_SYNTHETIC_PROC_FEATURES, hv_partition_synthetic_processor_features, hv_register_assoc, - hv_register_name_HV_X64_REGISTER_RIP, hv_register_value, mshv_user_mem_region, + hv_register_name_HV_X64_REGISTER_RIP, hv_register_value, mshv_install_intercept, + mshv_user_mem_region, }; use mshv_ioctls::{Mshv, VcpuFd, VmFd}; use tracing::{Span, instrument}; @@ -108,6 +112,18 @@ impl MshvVm { } impl VirtualMachine for MshvVm { + fn enable_msr_intercept(&mut self) -> std::result::Result<(), CreateVmError> { + let intercept = mshv_install_intercept { + access_type_mask: HV_INTERCEPT_ACCESS_MASK_WRITE | HV_INTERCEPT_ACCESS_MASK_READ, + intercept_type: hv_intercept_type_HV_INTERCEPT_TYPE_X64_MSR, + intercept_parameter: Default::default(), + }; + self.vm_fd + .install_intercept(intercept) + .map_err(|e| CreateVmError::EnableMsrIntercept(e.into()))?; + Ok(()) + } + unsafe fn map_memory( &mut self, (_slot, region): (u32, &MemoryRegion), @@ -137,6 +153,7 @@ impl VirtualMachine for MshvVm { hv_message_type_HVMSG_X64_IO_PORT_INTERCEPT; const UNMAPPED_GPA_MESSAGE: hv_message_type = hv_message_type_HVMSG_UNMAPPED_GPA; const INVALID_GPA_ACCESS_MESSAGE: hv_message_type = hv_message_type_HVMSG_GPA_INTERCEPT; + const MSR_MESSAGE: hv_message_type = hv_message_type_HVMSG_X64_MSR_INTERCEPT; #[cfg(gdb)] const EXCEPTION_INTERCEPT: hv_message_type = hv_message_type_HVMSG_X64_EXCEPTION_INTERCEPT; @@ -196,6 +213,23 @@ impl VirtualMachine for MshvVm { _ => VmExit::Unknown("Unknown MMIO access".to_string()), } } + MSR_MESSAGE => { + let msr_message = m + .to_msr_info() + .map_err(|_| RunVcpuError::DecodeMsrMessage(m.header.message_type))?; + let edx = msr_message.rdx; + let eax = msr_message.rax; + let written_value = (edx << 32) | eax; + let access = msr_message.header.intercept_access_type as u32; + match access { + HV_INTERCEPT_ACCESS_READ => VmExit::MsrRead(msr_message.msr_number), + HV_INTERCEPT_ACCESS_WRITE => VmExit::MsrWrite { + msr_index: msr_message.msr_number, + value: written_value, + }, + _ => VmExit::Unknown(format!("Unknown MSR access type={}", access)), + } + } #[cfg(gdb)] EXCEPTION_INTERCEPT => { let ex_info = m diff --git a/src/hyperlight_host/src/hypervisor/virtual_machine/whp.rs b/src/hyperlight_host/src/hypervisor/virtual_machine/whp.rs index 9410e0d92..5b8de28be 100644 --- a/src/hyperlight_host/src/hypervisor/virtual_machine/whp.rs +++ b/src/hyperlight_host/src/hypervisor/virtual_machine/whp.rs @@ -132,6 +132,23 @@ impl WhpVm { } impl VirtualMachine for WhpVm { + fn enable_msr_intercept(&mut self) -> std::result::Result<(), CreateVmError> { + let mut extended_exits_property = WHV_PARTITION_PROPERTY::default(); + // X64MsrExit bit position (bit 1) in WHV_EXTENDED_VM_EXITS + // See https://learn.microsoft.com/en-us/virtualization/api/hypervisor-platform/funcs/whvpartitionpropertydatatypes + extended_exits_property.ExtendedVmExits.AsUINT64 = 1 << 1; + unsafe { + WHvSetPartitionProperty( + self.partition, + WHvPartitionPropertyCodeExtendedVmExits, + &extended_exits_property as *const _ as *const _, + std::mem::size_of::() as _, + ) + .map_err(|e| CreateVmError::EnableMsrIntercept(e.into()))? + }; + Ok(()) + } + unsafe fn map_memory( &mut self, (_slot, region): (u32, &MemoryRegion), @@ -271,6 +288,21 @@ impl VirtualMachine for WhpVm { } // Execution was cancelled by the host. WHvRunVpExitReasonCanceled => VmExit::Cancelled(), + WHvRunVpExitReasonX64MsrAccess => { + let msr_access = unsafe { exit_context.Anonymous.MsrAccess }; + let eax = msr_access.Rax; + let edx = msr_access.Rdx; + let written_value = (edx << 32) | eax; + let access = unsafe { msr_access.AccessInfo.AsUINT32 }; + match access { + 0 => VmExit::MsrRead(msr_access.MsrNumber), + 1 => VmExit::MsrWrite { + msr_index: msr_access.MsrNumber, + value: written_value, + }, + _ => VmExit::Unknown(format!("Unknown MSR access type={}", access)), + } + } #[cfg(gdb)] WHvRunVpExitReasonException => { let exception = unsafe { exit_context.Anonymous.VpException }; diff --git a/src/hyperlight_host/src/sandbox/config.rs b/src/hyperlight_host/src/sandbox/config.rs index 044c5f2fb..78d3cf44a 100644 --- a/src/hyperlight_host/src/sandbox/config.rs +++ b/src/hyperlight_host/src/sandbox/config.rs @@ -74,6 +74,8 @@ pub struct SandboxConfiguration { interrupt_vcpu_sigrtmin_offset: u8, /// How much writable memory to offer the guest scratch_size: usize, + /// Allow MSR (Model Specific Register) access. This is disabled by default for security reasons. + allow_msr: bool, } impl SandboxConfiguration { @@ -118,6 +120,7 @@ impl SandboxConfiguration { guest_debug_info, #[cfg(crashdump)] guest_core_dump, + allow_msr: false, } } @@ -159,6 +162,23 @@ impl SandboxConfiguration { self.interrupt_vcpu_sigrtmin_offset } + /// Set whether MSR access is allowed. By default, MSR access is disabled for security reasons. + /// + /// # Safety + /// + /// If enabled, restoring a snapshot will not reset MSRs set by the guest. + /// This can result in data leaks between different guest executions (across a snapshot restore). + #[instrument(skip_all, parent = Span::current(), level= "Trace")] + pub unsafe fn set_allow_msr(&mut self, allow_msr: bool) { + self.allow_msr = allow_msr; + } + + /// Get whether MSR access is allowed + #[instrument(skip_all, parent = Span::current(), level= "Trace")] + pub(crate) fn get_allow_msr(&self) -> bool { + self.allow_msr + } + /// Sets the offset from `SIGRTMIN` to determine the real-time signal used for /// interrupting the VCPU thread. /// diff --git a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs index 138ebdaf6..ef7184c7a 100644 --- a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs +++ b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs @@ -1433,4 +1433,78 @@ mod tests { drop(sbox); } } + + #[test] + fn test_read_write_msr() { + use rand::seq::IteratorRandom; + + let value: u64 = 0x0; + const N: usize = 100; + + // MSR index ranges sourced from the Linux kernel's msr-index.h: + // https://github.com/torvalds/linux/blob/dc855b77719fe452d670cae2cf64da1eb51f16cc/arch/x86/include/asm/msr-index.h + let msr_numbers = (0x0u32..0x800u32) // Intel architectural MSRs + .chain(0x40000000u32..0x40000100u32) // Hyper-V synthetic MSRs + .chain(0xC0000000u32..0xC0002000u32) // AMD64 MSRs (EFER, STAR, FS/GS_BASE, etc.) + .chain(0xC0010000u32..0xC0012000u32) // AMD K7/K8/Fam10-19h MSRs + .choose_multiple(&mut rand::rng(), N); + + let mut sbox = UninitializedSandbox::new( + GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")), + None, + ) + .unwrap() + .evolve() + .unwrap(); + let snapshot = sbox.snapshot().unwrap(); + for msr_number in msr_numbers { + let result = sbox.call::("ReadMSR", msr_number); + match &result { + Ok(val) => panic!( + "Expected RDMSR to MSR 0x{:X} to be intercepted, but it succeeded with value 0x{:X}", + msr_number, val + ), + Err(err) => assert!( + matches!(err, HyperlightError::MsrReadViolation(msr) if *msr == msr_number), + "RDMSR 0x{:X}: expected MsrReadViolation, got error: {:?}", + msr_number, + err + ), + } + sbox.restore(snapshot.clone()).unwrap(); + let result = sbox.call::<()>("WriteMSR", (msr_number, value)); + match &result { + Ok(_) => panic!( + "Expected WRMSR to MSR 0x{:X} to be intercepted, but it succeeded", + msr_number + ), + Err(err) => assert!( + matches!(err, HyperlightError::MsrWriteViolation(msr_idx, v) if *msr_idx == msr_number && *v == value), + "WRMSR 0x{:X}: expected MsrWriteViolation, got error: {:?}", + msr_number, + err + ), + } + sbox.restore(snapshot.clone()).unwrap(); + } + + // Also try case where MSR access is allowed + let mut cfg = SandboxConfiguration::default(); + unsafe { cfg.set_allow_msr(true) }; + + let mut sbox = UninitializedSandbox::new( + GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")), + Some(cfg), + ) + .unwrap() + .evolve() + .unwrap(); + + let msr_index: u32 = 0xC0000102; // IA32_KERNEL_GS_BASE + let value: u64 = 0x5; + + sbox.call::<()>("WriteMSR", (msr_index, value)).unwrap(); + let read_value: u64 = sbox.call("ReadMSR", msr_index).unwrap(); + assert_eq!(read_value, value); + } } diff --git a/src/tests/rust_guests/dummyguest/Cargo.lock b/src/tests/rust_guests/dummyguest/Cargo.lock index 3e2c4afef..fbf3bc62c 100644 --- a/src/tests/rust_guests/dummyguest/Cargo.lock +++ b/src/tests/rust_guests/dummyguest/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] name = "autocfg" diff --git a/src/tests/rust_guests/simpleguest/Cargo.lock b/src/tests/rust_guests/simpleguest/Cargo.lock index 63f55ecee..0faead807 100644 --- a/src/tests/rust_guests/simpleguest/Cargo.lock +++ b/src/tests/rust_guests/simpleguest/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] name = "bitflags" diff --git a/src/tests/rust_guests/simpleguest/src/main.rs b/src/tests/rust_guests/simpleguest/src/main.rs index 09f55212a..7bdd0d6ef 100644 --- a/src/tests/rust_guests/simpleguest/src/main.rs +++ b/src/tests/rust_guests/simpleguest/src/main.rs @@ -649,6 +649,36 @@ fn call_host_expect_error(hostfuncname: String) -> Result<()> { Ok(()) } +#[guest_function("ReadMSR")] +fn read_msr(msr: u32) -> u64 { + let (read_eax, read_edx): (u32, u32); + unsafe { + core::arch::asm!( + "rdmsr", + in("ecx") msr, + out("eax") read_eax, + out("edx") read_edx, + options(nostack, nomem) + ); + } + ((read_edx as u64) << 32) | (read_eax as u64) +} + +#[guest_function("WriteMSR")] +fn write_msr(msr: u32, value: u64) { + let eax = (value & 0xFFFFFFFF) as u32; + let edx = ((value >> 32) & 0xFFFFFFFF) as u32; + unsafe { + core::arch::asm!( + "wrmsr", + in("ecx") msr, + in("eax") eax, + in("edx") edx, + options(nostack, nomem) + ); + } +} + #[no_mangle] #[instrument(skip_all, parent = Span::current(), level= "Trace")] pub extern "C" fn hyperlight_main() { diff --git a/src/tests/rust_guests/witguest/Cargo.lock b/src/tests/rust_guests/witguest/Cargo.lock index 647cd7438..3715fc832 100644 --- a/src/tests/rust_guests/witguest/Cargo.lock +++ b/src/tests/rust_guests/witguest/Cargo.lock @@ -63,9 +63,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] name = "bitflags"