use std::ops::Range;
use bitflags::bitflags;
#[cfg(mshv3)]
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(mshv3)]
use mshv_bindings::{
MSHV_SET_MEM_BIT_EXECUTABLE, MSHV_SET_MEM_BIT_UNMAP, MSHV_SET_MEM_BIT_WRITABLE,
};
#[cfg(all(mshv3, target_arch = "aarch64"))]
use mshv_bindings::{hv_arm64_memory_intercept_message, mshv_user_mem_region};
#[cfg(all(mshv3, target_arch = "x86_64"))]
use mshv_bindings::{hv_x64_memory_intercept_message, mshv_user_mem_region};
#[cfg(target_os = "windows")]
use windows::Win32::System::Hypervisor::{self, WHV_MEMORY_ACCESS_TYPE};
#[cfg(target_os = "windows")]
use crate::hypervisor::wrappers::HandleWrapper;
pub(crate) const DEFAULT_GUEST_BLOB_MEM_FLAGS: MemoryRegionFlags = MemoryRegionFlags::READ;
bitflags! {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct MemoryRegionFlags: u32 {
const NONE = 0;
const READ = 1;
const WRITE = 2;
const EXECUTE = 4;
}
}
impl std::fmt::Display for MemoryRegionFlags {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_empty() {
write!(f, "NONE")
} else {
let mut first = true;
if self.contains(MemoryRegionFlags::READ) {
write!(f, "READ")?;
first = false;
}
if self.contains(MemoryRegionFlags::WRITE) {
if !first {
write!(f, " | ")?;
}
write!(f, "WRITE")?;
first = false;
}
if self.contains(MemoryRegionFlags::EXECUTE) {
if !first {
write!(f, " | ")?;
}
write!(f, "EXECUTE")?;
}
Ok(())
}
}
}
#[cfg(target_os = "windows")]
impl TryFrom<WHV_MEMORY_ACCESS_TYPE> for MemoryRegionFlags {
type Error = crate::HyperlightError;
fn try_from(flags: WHV_MEMORY_ACCESS_TYPE) -> crate::Result<Self> {
match flags {
Hypervisor::WHvMemoryAccessRead => Ok(MemoryRegionFlags::READ),
Hypervisor::WHvMemoryAccessWrite => Ok(MemoryRegionFlags::WRITE),
Hypervisor::WHvMemoryAccessExecute => Ok(MemoryRegionFlags::EXECUTE),
_ => Err(crate::HyperlightError::Error(
"unknown memory access type".to_string(),
)),
}
}
}
#[cfg(all(mshv3, target_arch = "x86_64"))]
impl TryFrom<hv_x64_memory_intercept_message> for MemoryRegionFlags {
type Error = crate::HyperlightError;
fn try_from(msg: hv_x64_memory_intercept_message) -> crate::Result<Self> {
let access_type = msg.header.intercept_access_type;
match access_type {
0 => Ok(MemoryRegionFlags::READ),
1 => Ok(MemoryRegionFlags::WRITE),
2 => Ok(MemoryRegionFlags::EXECUTE),
_ => Err(crate::HyperlightError::Error(
"unknown memory access type".to_string(),
)),
}
}
}
#[cfg(all(mshv3, target_arch = "aarch64"))]
impl TryFrom<hv_arm64_memory_intercept_message> for MemoryRegionFlags {
type Error = crate::HyperlightError;
fn try_from(_msg: hv_arm64_memory_intercept_message) -> crate::Result<Self> {
unimplemented!("try_from")
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
pub enum MemoryRegionType {
Code,
InitData,
Peb,
Heap,
Scratch,
Snapshot,
MappedFile,
}
#[cfg(target_os = "windows")]
impl MemoryRegionType {
pub fn surrogate_mapping(&self) -> SurrogateMapping {
match self {
MemoryRegionType::MappedFile => SurrogateMapping::ReadOnlyFile,
_ => SurrogateMapping::SandboxMemory,
}
}
}
pub trait MemoryRegionKind {
type HostBaseType: Copy;
fn add(base: Self::HostBaseType, size: usize) -> Self::HostBaseType;
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
pub struct HostGuestMemoryRegion {}
#[cfg(not(target_os = "windows"))]
impl MemoryRegionKind for HostGuestMemoryRegion {
type HostBaseType = usize;
fn add(base: Self::HostBaseType, size: usize) -> Self::HostBaseType {
base + size
}
}
#[cfg(target_os = "windows")]
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
pub enum SurrogateMapping {
SandboxMemory,
ReadOnlyFile,
}
#[cfg(target_os = "windows")]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct HostRegionBase {
pub from_handle: HandleWrapper,
pub handle_base: usize,
pub handle_size: usize,
pub offset: usize,
}
#[cfg(target_os = "windows")]
impl std::hash::Hash for HostRegionBase {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.handle_base.hash(state);
self.handle_size.hash(state);
self.offset.hash(state);
}
}
#[cfg(target_os = "windows")]
impl From<HostRegionBase> for usize {
fn from(x: HostRegionBase) -> usize {
x.handle_base + x.offset
}
}
#[cfg(target_os = "windows")]
impl TryFrom<HostRegionBase> for isize {
type Error = <isize as TryFrom<usize>>::Error;
fn try_from(x: HostRegionBase) -> Result<isize, Self::Error> {
<isize as TryFrom<usize>>::try_from(x.into())
}
}
#[cfg(target_os = "windows")]
impl MemoryRegionKind for HostGuestMemoryRegion {
type HostBaseType = HostRegionBase;
fn add(base: Self::HostBaseType, size: usize) -> Self::HostBaseType {
HostRegionBase {
from_handle: base.from_handle,
handle_base: base.handle_base,
handle_size: base.handle_size,
offset: base.offset + size,
}
}
}
#[cfg_attr(feature = "nanvix-unstable", allow(dead_code))]
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
pub(crate) struct GuestMemoryRegion {}
impl MemoryRegionKind for GuestMemoryRegion {
type HostBaseType = ();
fn add(_base: Self::HostBaseType, _size: usize) -> Self::HostBaseType {}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MemoryRegion_<K: MemoryRegionKind> {
pub guest_region: Range<usize>,
pub host_region: Range<K::HostBaseType>,
pub flags: MemoryRegionFlags,
pub region_type: MemoryRegionType,
}
pub type MemoryRegion = MemoryRegion_<HostGuestMemoryRegion>;
#[cfg(crashdump)]
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
pub(crate) struct CrashDumpMemoryRegion;
#[cfg(crashdump)]
impl MemoryRegionKind for CrashDumpMemoryRegion {
type HostBaseType = usize;
fn add(base: Self::HostBaseType, size: usize) -> Self::HostBaseType {
base + size
}
}
#[cfg(crashdump)]
pub(crate) type CrashDumpRegion = MemoryRegion_<CrashDumpMemoryRegion>;
#[cfg(all(crashdump, feature = "nanvix-unstable"))]
impl HostGuestMemoryRegion {
pub(crate) fn to_addr(val: <Self as MemoryRegionKind>::HostBaseType) -> usize {
#[cfg(not(target_os = "windows"))]
{
val
}
#[cfg(target_os = "windows")]
{
val.into()
}
}
}
#[cfg_attr(feature = "nanvix-unstable", allow(unused))]
pub(crate) struct MemoryRegionVecBuilder<K: MemoryRegionKind> {
guest_base_phys_addr: usize,
host_base_virt_addr: K::HostBaseType,
regions: Vec<MemoryRegion_<K>>,
}
impl<K: MemoryRegionKind> MemoryRegionVecBuilder<K> {
pub(crate) fn new(guest_base_phys_addr: usize, host_base_virt_addr: K::HostBaseType) -> Self {
Self {
guest_base_phys_addr,
host_base_virt_addr,
regions: Vec::new(),
}
}
fn push(
&mut self,
size: usize,
flags: MemoryRegionFlags,
region_type: MemoryRegionType,
) -> usize {
if self.regions.is_empty() {
let guest_end = self.guest_base_phys_addr + size;
let host_end = <K as MemoryRegionKind>::add(self.host_base_virt_addr, size);
self.regions.push(MemoryRegion_ {
guest_region: self.guest_base_phys_addr..guest_end,
host_region: self.host_base_virt_addr..host_end,
flags,
region_type,
});
return guest_end - self.guest_base_phys_addr;
}
#[allow(clippy::unwrap_used)]
let last_region = self.regions.last().unwrap();
let host_end = <K as MemoryRegionKind>::add(last_region.host_region.end, size);
let new_region = MemoryRegion_ {
guest_region: last_region.guest_region.end..last_region.guest_region.end + size,
host_region: last_region.host_region.end..host_end,
flags,
region_type,
};
let ret = new_region.guest_region.end;
self.regions.push(new_region);
ret - self.guest_base_phys_addr
}
pub(crate) fn push_page_aligned(
&mut self,
size: usize,
flags: MemoryRegionFlags,
region_type: MemoryRegionType,
) -> usize {
let aligned_size = (size + PAGE_SIZE_USIZE - 1) & !(PAGE_SIZE_USIZE - 1);
self.push(aligned_size, flags, region_type)
}
pub(crate) fn build(self) -> Vec<MemoryRegion_<K>> {
self.regions
}
}
#[cfg(mshv3)]
impl From<&MemoryRegion> for mshv_user_mem_region {
fn from(region: &MemoryRegion) -> Self {
let size = (region.guest_region.end - region.guest_region.start) as u64;
let guest_pfn = region.guest_region.start as u64 >> PAGE_SHIFT;
let userspace_addr = region.host_region.start as u64;
let flags: u8 = region.flags.iter().fold(0, |acc, flag| {
let flag_value = match flag {
MemoryRegionFlags::NONE => 1 << MSHV_SET_MEM_BIT_UNMAP,
MemoryRegionFlags::READ => 0,
MemoryRegionFlags::WRITE => 1 << MSHV_SET_MEM_BIT_WRITABLE,
MemoryRegionFlags::EXECUTE => 1 << MSHV_SET_MEM_BIT_EXECUTABLE,
_ => 0, };
acc | flag_value
});
mshv_user_mem_region {
guest_pfn,
size,
userspace_addr,
flags,
..Default::default()
}
}
}
#[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: if perm_flags.contains(MemoryRegionFlags::WRITE) {
0 } else {
KVM_MEM_READONLY },
}
}
}