use core::ffi::c_void;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use hyperlight_common::mem::PAGE_SIZE_USIZE;
use tracing::{Span, instrument};
use windows::Win32::Foundation::HANDLE;
use windows::Win32::System::Memory::{
MEMORY_MAPPED_VIEW_ADDRESS, MapViewOfFileNuma2, PAGE_NOACCESS, PAGE_PROTECTION_FLAGS,
PAGE_READONLY, PAGE_READWRITE, UNMAP_VIEW_OF_FILE_FLAGS, UnmapViewOfFile2, VirtualProtectEx,
};
use windows::Win32::System::SystemServices::NUMA_NO_PREFERRED_NODE;
use super::surrogate_process_manager::get_surrogate_process_manager;
use super::wrappers::HandleWrapper;
use crate::HyperlightError::WindowsAPIError;
use crate::mem::memory_region::SurrogateMapping;
use crate::{Result, log_then_return};
#[derive(Debug)]
pub(crate) struct HandleMapping {
pub(crate) use_count: u64,
pub(crate) surrogate_base: *mut c_void,
pub(crate) mapping_type: SurrogateMapping,
}
#[derive(Debug)]
pub(super) struct SurrogateProcess {
pub(crate) mappings: HashMap<usize, HandleMapping>,
pub(crate) process_handle: HandleWrapper,
}
impl SurrogateProcess {
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
pub(super) fn new(process_handle: HANDLE) -> Self {
Self {
mappings: HashMap::new(),
process_handle: HandleWrapper::from(process_handle),
}
}
pub(super) fn map(
&mut self,
handle: HandleWrapper,
host_base: usize,
host_size: usize,
mapping: &SurrogateMapping,
) -> Result<*mut c_void> {
match self.mappings.entry(host_base) {
Entry::Occupied(mut oe) => {
if oe.get().mapping_type != *mapping {
tracing::warn!(
"Conflicting SurrogateMapping for host_base {host_base:#x}: \
existing={:?}, requested={:?}",
oe.get().mapping_type,
mapping
);
}
oe.get_mut().use_count += 1;
Ok(oe.get().surrogate_base)
}
Entry::Vacant(ve) => {
let page_protection = match mapping {
SurrogateMapping::SandboxMemory => PAGE_READWRITE,
SurrogateMapping::ReadOnlyFile => PAGE_READONLY,
};
let surrogate_base = unsafe {
MapViewOfFileNuma2(
handle.into(),
self.process_handle.into(),
0,
None,
host_size,
0,
page_protection.0,
NUMA_NO_PREFERRED_NODE,
)
};
if surrogate_base.Value.is_null() {
log_then_return!(
"MapViewOfFileNuma2 failed: {:?}",
std::io::Error::last_os_error()
);
}
if *mapping == SurrogateMapping::SandboxMemory {
let mut unused_out_old_prot_flags = PAGE_PROTECTION_FLAGS(0);
let first_guard_page_start = surrogate_base.Value;
if let Err(e) = unsafe {
VirtualProtectEx(
self.process_handle.into(),
first_guard_page_start,
PAGE_SIZE_USIZE,
PAGE_NOACCESS,
&mut unused_out_old_prot_flags,
)
} {
self.unmap_helper(surrogate_base.Value);
log_then_return!(WindowsAPIError(e.clone()));
}
let last_guard_page_start =
unsafe { first_guard_page_start.add(host_size - PAGE_SIZE_USIZE) };
if let Err(e) = unsafe {
VirtualProtectEx(
self.process_handle.into(),
last_guard_page_start,
PAGE_SIZE_USIZE,
PAGE_NOACCESS,
&mut unused_out_old_prot_flags,
)
} {
self.unmap_helper(surrogate_base.Value);
log_then_return!(WindowsAPIError(e.clone()));
}
}
ve.insert(HandleMapping {
use_count: 1,
surrogate_base: surrogate_base.Value,
mapping_type: *mapping,
});
Ok(surrogate_base.Value)
}
}
}
pub(super) fn unmap(&mut self, host_base: usize) {
match self.mappings.entry(host_base) {
Entry::Occupied(mut oe) => {
oe.get_mut().use_count = oe.get().use_count.checked_sub(1).unwrap_or_else(|| {
tracing::error!(
"Surrogate unmap ref count underflow for host_base {:#x}",
host_base
);
0
});
if oe.get().use_count == 0 {
let entry = oe.remove();
self.unmap_helper(entry.surrogate_base);
}
}
Entry::Vacant(_) => {
tracing::error!(
"Attempted to unmap from surrogate a region at host_base {:#x} that was never mapped",
host_base
);
#[cfg(debug_assertions)]
panic!("Attempted to unmap from surrogate a region that was never mapped");
}
}
}
fn unmap_helper(&self, surrogate_base: *mut c_void) {
let memory_mapped_view_address = MEMORY_MAPPED_VIEW_ADDRESS {
Value: surrogate_base,
};
let flags = UNMAP_VIEW_OF_FILE_FLAGS(0);
if let Err(e) = unsafe {
UnmapViewOfFile2(
self.process_handle.into(),
memory_mapped_view_address,
flags,
)
} {
tracing::error!(
"Failed to free surrogate process resources (UnmapViewOfFile2 failed): {:?}",
e
);
}
}
}
impl Default for SurrogateProcess {
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
fn default() -> Self {
Self::new(Default::default())
}
}
impl Drop for SurrogateProcess {
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
fn drop(&mut self) {
for mapping in self.mappings.values() {
self.unmap_helper(mapping.surrogate_base);
}
match get_surrogate_process_manager() {
Ok(manager) => match manager.return_surrogate_process(self.process_handle) {
Ok(_) => (),
Err(e) => {
tracing::error!(
"Failed to return surrogate process to surrogate process manager when dropping : {:?}",
e
);
}
},
Err(e) => {
tracing::error!(
"Failed to get surrogate process manager when dropping SurrogateProcess: {:?}",
e
);
}
}
}
}