Skip to content

Add memory mapping support with KVM #709

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 39 additions & 23 deletions src/hyperlight_host/src/hypervisor/kvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) }
})?;

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -463,6 +453,8 @@ impl Hypervisor for KVMDriver {
max_guest_log_level: Option<LevelFilter>,
#[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(),
Expand Down Expand Up @@ -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(())
}
Expand Down
23 changes: 23 additions & 0 deletions src/hyperlight_host/src/mem/memory_region.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -308,3 +310,24 @@ impl From<MemoryRegion> for mshv_user_mem_region {
}
}
}

#[cfg(kvm)]
impl From<MemoryRegion> 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
},
}
}
}
61 changes: 61 additions & 0 deletions src/hyperlight_host/src/sandbox/initialized_multi_use.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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(&region_for_memory(&map_mem, guest_base))
.unwrap();
}

let _guard = map_mem.lock.try_read().unwrap();
let actual: Vec<u8> = 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,
}
}
}
32 changes: 32 additions & 0 deletions src/tests/rust_guests/simpleguest/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,29 @@ fn read_from_user_memory(function_call: &FunctionCall) -> Result<Vec<u8>> {
}
}

fn read_mapped_buffer(function_call: &FunctionCall) -> Result<Vec<u8>> {
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(
Expand All @@ -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(),
Expand Down
Loading