diff --git a/src/hyperlight_host/src/hypervisor/kvm.rs b/src/hyperlight_host/src/hypervisor/kvm.rs index 3da9786cd..0802ecb6b 100644 --- a/src/hyperlight_host/src/hypervisor/kvm.rs +++ b/src/hyperlight_host/src/hypervisor/kvm.rs @@ -21,7 +21,7 @@ use std::sync::Arc; use std::sync::Mutex; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use kvm_bindings::{KVM_MEM_READONLY, kvm_fpu, kvm_regs, kvm_userspace_memory_region}; +use kvm_bindings::{kvm_fpu, kvm_regs, kvm_userspace_memory_region}; use kvm_ioctls::Cap::UserMemory; use kvm_ioctls::{Kvm, VcpuExit, VcpuFd, VmFd}; use log::LevelFilter; @@ -284,7 +284,8 @@ mod debug { /// A Hypervisor driver for KVM on Linux pub(crate) struct KVMDriver { _kvm: Kvm, - _vm_fd: VmFd, + vm_fd: VmFd, + page_size: usize, vcpu_fd: VcpuFd, entrypoint: u64, orig_rsp: GuestPtr, @@ -317,21 +318,9 @@ impl KVMDriver { let vm_fd = kvm.create_vm_with_type(0)?; - let perm_flags = - MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE; - mem_regions.iter().enumerate().try_for_each(|(i, region)| { - let perm_flags = perm_flags.intersection(region.flags); - let kvm_region = kvm_userspace_memory_region { - slot: i as u32, - guest_phys_addr: region.guest_region.start as u64, - memory_size: (region.guest_region.end - region.guest_region.start) as u64, - userspace_addr: region.host_region.start as u64, - flags: match perm_flags { - MemoryRegionFlags::READ => KVM_MEM_READONLY, - _ => 0, // normal, RWX - }, - }; + let mut kvm_region: kvm_userspace_memory_region = region.clone().into(); + kvm_region.slot = i as u32; unsafe { vm_fd.set_user_memory_region(kvm_region) } })?; @@ -378,7 +367,8 @@ impl KVMDriver { #[allow(unused_mut)] let mut hv = Self { _kvm: kvm, - _vm_fd: vm_fd, + vm_fd, + page_size: 0, vcpu_fd, entrypoint, orig_rsp: rsp_gp, @@ -463,6 +453,8 @@ impl Hypervisor for KVMDriver { max_guest_log_level: Option, #[cfg(gdb)] dbg_mem_access_fn: DbgMemAccessHandlerWrapper, ) -> Result<()> { + self.page_size = page_size as usize; + let max_guest_log_level: u64 = match max_guest_log_level { Some(level) => level as u64, None => self.get_max_log_level().into(), @@ -494,16 +486,40 @@ impl Hypervisor for KVMDriver { } #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] - unsafe fn map_region(&mut self, _rgn: &MemoryRegion) -> Result<()> { - log_then_return!("Mapping host memory into the guest not yet supported on this platform"); + unsafe fn map_region(&mut self, region: &MemoryRegion) -> Result<()> { + if [ + region.guest_region.start, + region.guest_region.end, + region.host_region.start, + region.host_region.end, + ] + .iter() + .any(|x| x % self.page_size != 0) + { + log_then_return!( + "region is not page-aligned {:x}, {region:?}", + self.page_size + ); + } + + let mut kvm_region: kvm_userspace_memory_region = region.clone().into(); + kvm_region.slot = self.mem_regions.len() as u32; + unsafe { self.vm_fd.set_user_memory_region(kvm_region) }?; + self.mem_regions.push(region.to_owned()); + Ok(()) } #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] unsafe fn unmap_regions(&mut self, n: u64) -> Result<()> { - if n > 0 { - log_then_return!( - "Mapping host memory into the guest not yet supported on this platform" - ); + let n_keep = self.mem_regions.len() - n as usize; + for (k, region) in self.mem_regions.split_off(n_keep).iter().enumerate() { + let mut kvm_region: kvm_userspace_memory_region = region.clone().into(); + kvm_region.slot = (n_keep + k) as u32; + // Setting memory_size to 0 unmaps the slot's region + // From https://docs.kernel.org/virt/kvm/api.html + // > Deleting a slot is done by passing zero for memory_size. + kvm_region.memory_size = 0; + unsafe { self.vm_fd.set_user_memory_region(kvm_region) }?; } Ok(()) } diff --git a/src/hyperlight_host/src/mem/memory_region.rs b/src/hyperlight_host/src/mem/memory_region.rs index c24ac197c..b46426c3b 100644 --- a/src/hyperlight_host/src/mem/memory_region.rs +++ b/src/hyperlight_host/src/mem/memory_region.rs @@ -30,6 +30,8 @@ use bitflags::bitflags; #[cfg(mshv)] use hyperlight_common::mem::PAGE_SHIFT; use hyperlight_common::mem::PAGE_SIZE_USIZE; +#[cfg(kvm)] +use kvm_bindings::{KVM_MEM_READONLY, kvm_userspace_memory_region}; #[cfg(mshv2)] use mshv_bindings::{ HV_MAP_GPA_EXECUTABLE, HV_MAP_GPA_PERMISSIONS_NONE, HV_MAP_GPA_READABLE, HV_MAP_GPA_WRITABLE, @@ -308,3 +310,24 @@ impl From for mshv_user_mem_region { } } } + +#[cfg(kvm)] +impl From for kvm_bindings::kvm_userspace_memory_region { + fn from(region: MemoryRegion) -> Self { + let perm_flags = + MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE; + + let perm_flags = perm_flags.intersection(region.flags); + + kvm_userspace_memory_region { + slot: 0, + guest_phys_addr: region.guest_region.start as u64, + memory_size: (region.guest_region.end - region.guest_region.start) as u64, + userspace_addr: region.host_region.start as u64, + flags: match perm_flags { + MemoryRegionFlags::READ => KVM_MEM_READONLY, + _ => 0, // normal, RWX + }, + } + } +} diff --git a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs index 8df9d08ef..c2959262b 100644 --- a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs +++ b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs @@ -402,6 +402,10 @@ mod tests { use hyperlight_testing::simple_guest_as_string; use crate::func::call_ctx::MultiUseGuestCallContext; + #[cfg(target_os = "linux")] + use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags, MemoryRegionType}; + #[cfg(target_os = "linux")] + use crate::mem::shared_mem::{ExclusiveSharedMemory, GuestSharedMemory, SharedMemory as _}; use crate::sandbox::{Callable, SandboxConfiguration}; use crate::sandbox_state::sandbox::{DevolvableSandbox, EvolvableSandbox}; use crate::sandbox_state::transition::{MultiUseContextCallback, Noop}; @@ -693,4 +697,61 @@ mod tests { handle.join().unwrap(); } } + + #[cfg(target_os = "linux")] + #[test] + fn test_mmap() { + let mut sbox = UninitializedSandbox::new( + GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")), + None, + ) + .unwrap() + .evolve(Noop::default()) + .unwrap(); + + let expected = b"hello world"; + let map_mem = page_aligned_memory(expected); + let guest_base = 0x1_0000_0000; // Arbitrary guest base address + + unsafe { + sbox.map_region(®ion_for_memory(&map_mem, guest_base)) + .unwrap(); + } + + let _guard = map_mem.lock.try_read().unwrap(); + let actual: Vec = sbox + .call_guest_function_by_name( + "ReadMappedBuffer", + (guest_base as u64, expected.len() as u64), + ) + .unwrap(); + + assert_eq!(actual, expected); + } + + #[cfg(target_os = "linux")] + fn page_aligned_memory(src: &[u8]) -> GuestSharedMemory { + use hyperlight_common::mem::PAGE_SIZE_USIZE; + + let len = src.len().div_ceil(PAGE_SIZE_USIZE) * PAGE_SIZE_USIZE; + + let mut mem = ExclusiveSharedMemory::new(len).unwrap(); + mem.copy_from_slice(src, 0).unwrap(); + + let (_, guest_mem) = mem.build(); + + guest_mem + } + + #[cfg(target_os = "linux")] + fn region_for_memory(mem: &GuestSharedMemory, guest_base: usize) -> MemoryRegion { + let ptr = mem.base_addr(); + let len = mem.mem_size(); + MemoryRegion { + host_region: ptr..(ptr + len), + guest_region: guest_base..(guest_base + len), + flags: MemoryRegionFlags::READ, + region_type: MemoryRegionType::Heap, + } + } } diff --git a/src/tests/rust_guests/simpleguest/src/main.rs b/src/tests/rust_guests/simpleguest/src/main.rs index 1cd276dd6..3bf093665 100644 --- a/src/tests/rust_guests/simpleguest/src/main.rs +++ b/src/tests/rust_guests/simpleguest/src/main.rs @@ -775,6 +775,29 @@ fn read_from_user_memory(function_call: &FunctionCall) -> Result> { } } +fn read_mapped_buffer(function_call: &FunctionCall) -> Result> { + if let (ParameterValue::ULong(base), ParameterValue::ULong(len)) = ( + function_call.parameters.clone().unwrap()[0].clone(), + function_call.parameters.clone().unwrap()[1].clone(), + ) { + let base = base as usize as *const u8; + let len = len as usize; + + unsafe { + hyperlight_guest_bin::paging::map_region(base as _, base as _, len as u64 + 4096) + }; + + let data = unsafe { core::slice::from_raw_parts(base, len) }; + + Ok(get_flatbuffer_result(data)) + } else { + Err(HyperlightGuestError::new( + ErrorCode::GuestFunctionParameterTypeMismatch, + "Invalid parameters passed to read_mapped_buffer".to_string(), + )) + } +} + #[no_mangle] pub extern "C" fn hyperlight_main() { let read_from_user_memory_def = GuestFunctionDefinition::new( @@ -786,6 +809,15 @@ pub extern "C" fn hyperlight_main() { register_function(read_from_user_memory_def); + let read_mapped_buffer_def = GuestFunctionDefinition::new( + "ReadMappedBuffer".to_string(), + Vec::from(&[ParameterType::ULong, ParameterType::ULong]), + ReturnType::VecBytes, + read_mapped_buffer as usize, + ); + + register_function(read_mapped_buffer_def); + let set_static_def = GuestFunctionDefinition::new( "SetStatic".to_string(), Vec::new(),