Skip to content

Commit 39df600

Browse files
authored
add memory mapping support with kvm (#709)
Signed-off-by: Jorge Prendes <jorge.prendes@gmail.com>
1 parent ea6fa8f commit 39df600

File tree

4 files changed

+155
-23
lines changed

4 files changed

+155
-23
lines changed

src/hyperlight_host/src/hypervisor/kvm.rs

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use std::sync::Arc;
2121
use std::sync::Mutex;
2222
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
2323

24-
use kvm_bindings::{KVM_MEM_READONLY, kvm_fpu, kvm_regs, kvm_userspace_memory_region};
24+
use kvm_bindings::{kvm_fpu, kvm_regs, kvm_userspace_memory_region};
2525
use kvm_ioctls::Cap::UserMemory;
2626
use kvm_ioctls::{Kvm, VcpuExit, VcpuFd, VmFd};
2727
use log::LevelFilter;
@@ -284,7 +284,8 @@ mod debug {
284284
/// A Hypervisor driver for KVM on Linux
285285
pub(crate) struct KVMDriver {
286286
_kvm: Kvm,
287-
_vm_fd: VmFd,
287+
vm_fd: VmFd,
288+
page_size: usize,
288289
vcpu_fd: VcpuFd,
289290
entrypoint: u64,
290291
orig_rsp: GuestPtr,
@@ -317,21 +318,9 @@ impl KVMDriver {
317318

318319
let vm_fd = kvm.create_vm_with_type(0)?;
319320

320-
let perm_flags =
321-
MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE;
322-
323321
mem_regions.iter().enumerate().try_for_each(|(i, region)| {
324-
let perm_flags = perm_flags.intersection(region.flags);
325-
let kvm_region = kvm_userspace_memory_region {
326-
slot: i as u32,
327-
guest_phys_addr: region.guest_region.start as u64,
328-
memory_size: (region.guest_region.end - region.guest_region.start) as u64,
329-
userspace_addr: region.host_region.start as u64,
330-
flags: match perm_flags {
331-
MemoryRegionFlags::READ => KVM_MEM_READONLY,
332-
_ => 0, // normal, RWX
333-
},
334-
};
322+
let mut kvm_region: kvm_userspace_memory_region = region.clone().into();
323+
kvm_region.slot = i as u32;
335324
unsafe { vm_fd.set_user_memory_region(kvm_region) }
336325
})?;
337326

@@ -378,7 +367,8 @@ impl KVMDriver {
378367
#[allow(unused_mut)]
379368
let mut hv = Self {
380369
_kvm: kvm,
381-
_vm_fd: vm_fd,
370+
vm_fd,
371+
page_size: 0,
382372
vcpu_fd,
383373
entrypoint,
384374
orig_rsp: rsp_gp,
@@ -463,6 +453,8 @@ impl Hypervisor for KVMDriver {
463453
max_guest_log_level: Option<LevelFilter>,
464454
#[cfg(gdb)] dbg_mem_access_fn: DbgMemAccessHandlerWrapper,
465455
) -> Result<()> {
456+
self.page_size = page_size as usize;
457+
466458
let max_guest_log_level: u64 = match max_guest_log_level {
467459
Some(level) => level as u64,
468460
None => self.get_max_log_level().into(),
@@ -494,16 +486,40 @@ impl Hypervisor for KVMDriver {
494486
}
495487

496488
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
497-
unsafe fn map_region(&mut self, _rgn: &MemoryRegion) -> Result<()> {
498-
log_then_return!("Mapping host memory into the guest not yet supported on this platform");
489+
unsafe fn map_region(&mut self, region: &MemoryRegion) -> Result<()> {
490+
if [
491+
region.guest_region.start,
492+
region.guest_region.end,
493+
region.host_region.start,
494+
region.host_region.end,
495+
]
496+
.iter()
497+
.any(|x| x % self.page_size != 0)
498+
{
499+
log_then_return!(
500+
"region is not page-aligned {:x}, {region:?}",
501+
self.page_size
502+
);
503+
}
504+
505+
let mut kvm_region: kvm_userspace_memory_region = region.clone().into();
506+
kvm_region.slot = self.mem_regions.len() as u32;
507+
unsafe { self.vm_fd.set_user_memory_region(kvm_region) }?;
508+
self.mem_regions.push(region.to_owned());
509+
Ok(())
499510
}
500511

501512
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
502513
unsafe fn unmap_regions(&mut self, n: u64) -> Result<()> {
503-
if n > 0 {
504-
log_then_return!(
505-
"Mapping host memory into the guest not yet supported on this platform"
506-
);
514+
let n_keep = self.mem_regions.len() - n as usize;
515+
for (k, region) in self.mem_regions.split_off(n_keep).iter().enumerate() {
516+
let mut kvm_region: kvm_userspace_memory_region = region.clone().into();
517+
kvm_region.slot = (n_keep + k) as u32;
518+
// Setting memory_size to 0 unmaps the slot's region
519+
// From https://docs.kernel.org/virt/kvm/api.html
520+
// > Deleting a slot is done by passing zero for memory_size.
521+
kvm_region.memory_size = 0;
522+
unsafe { self.vm_fd.set_user_memory_region(kvm_region) }?;
507523
}
508524
Ok(())
509525
}

src/hyperlight_host/src/mem/memory_region.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ use bitflags::bitflags;
3030
#[cfg(mshv)]
3131
use hyperlight_common::mem::PAGE_SHIFT;
3232
use hyperlight_common::mem::PAGE_SIZE_USIZE;
33+
#[cfg(kvm)]
34+
use kvm_bindings::{KVM_MEM_READONLY, kvm_userspace_memory_region};
3335
#[cfg(mshv2)]
3436
use mshv_bindings::{
3537
HV_MAP_GPA_EXECUTABLE, HV_MAP_GPA_PERMISSIONS_NONE, HV_MAP_GPA_READABLE, HV_MAP_GPA_WRITABLE,
@@ -308,3 +310,24 @@ impl From<MemoryRegion> for mshv_user_mem_region {
308310
}
309311
}
310312
}
313+
314+
#[cfg(kvm)]
315+
impl From<MemoryRegion> for kvm_bindings::kvm_userspace_memory_region {
316+
fn from(region: MemoryRegion) -> Self {
317+
let perm_flags =
318+
MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE;
319+
320+
let perm_flags = perm_flags.intersection(region.flags);
321+
322+
kvm_userspace_memory_region {
323+
slot: 0,
324+
guest_phys_addr: region.guest_region.start as u64,
325+
memory_size: (region.guest_region.end - region.guest_region.start) as u64,
326+
userspace_addr: region.host_region.start as u64,
327+
flags: match perm_flags {
328+
MemoryRegionFlags::READ => KVM_MEM_READONLY,
329+
_ => 0, // normal, RWX
330+
},
331+
}
332+
}
333+
}

src/hyperlight_host/src/sandbox/initialized_multi_use.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,10 @@ mod tests {
402402
use hyperlight_testing::simple_guest_as_string;
403403

404404
use crate::func::call_ctx::MultiUseGuestCallContext;
405+
#[cfg(target_os = "linux")]
406+
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags, MemoryRegionType};
407+
#[cfg(target_os = "linux")]
408+
use crate::mem::shared_mem::{ExclusiveSharedMemory, GuestSharedMemory, SharedMemory as _};
405409
use crate::sandbox::{Callable, SandboxConfiguration};
406410
use crate::sandbox_state::sandbox::{DevolvableSandbox, EvolvableSandbox};
407411
use crate::sandbox_state::transition::{MultiUseContextCallback, Noop};
@@ -693,4 +697,61 @@ mod tests {
693697
handle.join().unwrap();
694698
}
695699
}
700+
701+
#[cfg(target_os = "linux")]
702+
#[test]
703+
fn test_mmap() {
704+
let mut sbox = UninitializedSandbox::new(
705+
GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
706+
None,
707+
)
708+
.unwrap()
709+
.evolve(Noop::default())
710+
.unwrap();
711+
712+
let expected = b"hello world";
713+
let map_mem = page_aligned_memory(expected);
714+
let guest_base = 0x1_0000_0000; // Arbitrary guest base address
715+
716+
unsafe {
717+
sbox.map_region(&region_for_memory(&map_mem, guest_base))
718+
.unwrap();
719+
}
720+
721+
let _guard = map_mem.lock.try_read().unwrap();
722+
let actual: Vec<u8> = sbox
723+
.call_guest_function_by_name(
724+
"ReadMappedBuffer",
725+
(guest_base as u64, expected.len() as u64),
726+
)
727+
.unwrap();
728+
729+
assert_eq!(actual, expected);
730+
}
731+
732+
#[cfg(target_os = "linux")]
733+
fn page_aligned_memory(src: &[u8]) -> GuestSharedMemory {
734+
use hyperlight_common::mem::PAGE_SIZE_USIZE;
735+
736+
let len = src.len().div_ceil(PAGE_SIZE_USIZE) * PAGE_SIZE_USIZE;
737+
738+
let mut mem = ExclusiveSharedMemory::new(len).unwrap();
739+
mem.copy_from_slice(src, 0).unwrap();
740+
741+
let (_, guest_mem) = mem.build();
742+
743+
guest_mem
744+
}
745+
746+
#[cfg(target_os = "linux")]
747+
fn region_for_memory(mem: &GuestSharedMemory, guest_base: usize) -> MemoryRegion {
748+
let ptr = mem.base_addr();
749+
let len = mem.mem_size();
750+
MemoryRegion {
751+
host_region: ptr..(ptr + len),
752+
guest_region: guest_base..(guest_base + len),
753+
flags: MemoryRegionFlags::READ,
754+
region_type: MemoryRegionType::Heap,
755+
}
756+
}
696757
}

src/tests/rust_guests/simpleguest/src/main.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,29 @@ fn read_from_user_memory(function_call: &FunctionCall) -> Result<Vec<u8>> {
775775
}
776776
}
777777

778+
fn read_mapped_buffer(function_call: &FunctionCall) -> Result<Vec<u8>> {
779+
if let (ParameterValue::ULong(base), ParameterValue::ULong(len)) = (
780+
function_call.parameters.clone().unwrap()[0].clone(),
781+
function_call.parameters.clone().unwrap()[1].clone(),
782+
) {
783+
let base = base as usize as *const u8;
784+
let len = len as usize;
785+
786+
unsafe {
787+
hyperlight_guest_bin::paging::map_region(base as _, base as _, len as u64 + 4096)
788+
};
789+
790+
let data = unsafe { core::slice::from_raw_parts(base, len) };
791+
792+
Ok(get_flatbuffer_result(data))
793+
} else {
794+
Err(HyperlightGuestError::new(
795+
ErrorCode::GuestFunctionParameterTypeMismatch,
796+
"Invalid parameters passed to read_mapped_buffer".to_string(),
797+
))
798+
}
799+
}
800+
778801
#[no_mangle]
779802
pub extern "C" fn hyperlight_main() {
780803
let read_from_user_memory_def = GuestFunctionDefinition::new(
@@ -786,6 +809,15 @@ pub extern "C" fn hyperlight_main() {
786809

787810
register_function(read_from_user_memory_def);
788811

812+
let read_mapped_buffer_def = GuestFunctionDefinition::new(
813+
"ReadMappedBuffer".to_string(),
814+
Vec::from(&[ParameterType::ULong, ParameterType::ULong]),
815+
ReturnType::VecBytes,
816+
read_mapped_buffer as usize,
817+
);
818+
819+
register_function(read_mapped_buffer_def);
820+
789821
let set_static_def = GuestFunctionDefinition::new(
790822
"SetStatic".to_string(),
791823
Vec::new(),

0 commit comments

Comments
 (0)