Skip to content

Commit da3e40b

Browse files
committed
Refactor host mapping to use RAII guards
Wrap host-mapping lifetime management in dedicated RAII guards. On Linux `Mmap` owns the `mmap` reservation and calls `munmap` on drop. On Windows `MappedView` owns the mapped view and calls `UnmapViewOfFile`, and `FileMapping` owns the file-mapping HANDLE and calls `CloseHandle`. `HostMapping` becomes a thin aggregate of these guards instead of carrying raw `(ptr, size, handle)` fields with a manual `Drop` impl, and exposes `ptr()`, `size()`, and `file_mapping_handle()` accessors that the `SharedMemory` trait defaults use. Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
1 parent 3afef57 commit da3e40b

1 file changed

Lines changed: 137 additions & 45 deletions

File tree

src/hyperlight_host/src/mem/shared_mem.rs

Lines changed: 137 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -93,36 +93,55 @@ macro_rules! generate_writer {
9393
/// Send or Sync, since it doesn't ensure any particular synchronization.
9494
#[derive(Debug)]
9595
pub struct HostMapping {
96-
ptr: *mut u8,
97-
size: usize,
96+
#[cfg(not(target_os = "windows"))]
97+
mmap: Mmap,
9898
#[cfg(target_os = "windows")]
99-
handle: HANDLE,
99+
mapping: WindowsMapping,
100100
}
101101

102-
impl Drop for HostMapping {
103-
#[cfg(target_os = "linux")]
104-
fn drop(&mut self) {
105-
use libc::munmap;
102+
/// Windows-side flavors of a [`HostMapping`].
103+
#[cfg(target_os = "windows")]
104+
#[derive(Debug)]
105+
enum WindowsMapping {
106+
/// `[guard][blob][guard]` carved from a single anonymous file
107+
/// mapping created via `CreateFileMappingA(INVALID_HANDLE_VALUE)`
108+
/// and mapped with `MapViewOfFile`.
109+
Anonymous {
110+
view: MappedView,
111+
file_mapping: FileMapping,
112+
},
113+
}
106114

107-
unsafe {
108-
munmap(self.ptr as *mut c_void, self.size);
115+
impl HostMapping {
116+
/// Base address of the host mapping, including the surrounding guard pages.
117+
pub(crate) fn ptr(&self) -> *mut u8 {
118+
#[cfg(not(target_os = "windows"))]
119+
{
120+
self.mmap.base as *mut u8
121+
}
122+
#[cfg(target_os = "windows")]
123+
match &self.mapping {
124+
WindowsMapping::Anonymous { view, .. } => view.addr as *mut u8,
109125
}
110126
}
111-
#[cfg(target_os = "windows")]
112-
fn drop(&mut self) {
113-
let mem_mapped_address = MEMORY_MAPPED_VIEW_ADDRESS {
114-
Value: self.ptr as *mut c_void,
115-
};
116-
if let Err(e) = unsafe { UnmapViewOfFile(mem_mapped_address) } {
117-
tracing::error!(
118-
"Failed to drop HostMapping (UnmapViewOfFile failed): {:?}",
119-
e
120-
);
127+
128+
/// Total size of the host mapping, including the surrounding guard pages.
129+
pub(crate) fn size(&self) -> usize {
130+
#[cfg(not(target_os = "windows"))]
131+
{
132+
self.mmap.len
133+
}
134+
#[cfg(target_os = "windows")]
135+
match &self.mapping {
136+
WindowsMapping::Anonymous { view, .. } => view.len,
121137
}
138+
}
122139

123-
let file_handle: HANDLE = self.handle;
124-
if let Err(e) = unsafe { CloseHandle(file_handle) } {
125-
tracing::error!("Failed to drop HostMapping (CloseHandle failed): {:?}", e);
140+
/// Win32 file-mapping handle backing this mapping.
141+
#[cfg(target_os = "windows")]
142+
pub(crate) fn file_mapping_handle(&self) -> HANDLE {
143+
match &self.mapping {
144+
WindowsMapping::Anonymous { file_mapping, .. } => file_mapping.0,
126145
}
127146
}
128147
}
@@ -384,19 +403,23 @@ impl ExclusiveSharedMemory {
384403
Error::last_os_error().raw_os_error()
385404
));
386405
}
406+
let mmap = Mmap {
407+
base: addr,
408+
len: total_size,
409+
};
387410

388411
// protect the guard pages
389412
#[cfg(not(miri))]
390413
{
391-
let res = unsafe { mprotect(addr, PAGE_SIZE_USIZE, PROT_NONE) };
414+
let res = unsafe { mprotect(mmap.base, PAGE_SIZE_USIZE, PROT_NONE) };
392415
if res != 0 {
393416
return Err(HyperlightError::MprotectFailed(
394417
Error::last_os_error().raw_os_error(),
395418
));
396419
}
397420
let res = unsafe {
398421
mprotect(
399-
(addr as *const u8).add(total_size - PAGE_SIZE_USIZE) as *mut c_void,
422+
(mmap.base as *const u8).add(total_size - PAGE_SIZE_USIZE) as *mut c_void,
400423
PAGE_SIZE_USIZE,
401424
PROT_NONE,
402425
)
@@ -418,10 +441,7 @@ impl ExclusiveSharedMemory {
418441
// type does have Send and Sync manually impl'd, the Arc
419442
// is not pointless as the lint suggests.
420443
#[allow(clippy::arc_with_non_send_sync)]
421-
region: Arc::new(HostMapping {
422-
ptr: addr as *mut u8,
423-
size: total_size,
424-
}),
444+
region: Arc::new(HostMapping { mmap }),
425445
})
426446
}
427447

@@ -485,23 +505,28 @@ impl ExclusiveSharedMemory {
485505
Error::last_os_error().raw_os_error()
486506
));
487507
}
508+
let file_mapping = FileMapping(handle);
488509

489510
let file_map = FILE_MAP_ALL_ACCESS;
490-
let addr = unsafe { MapViewOfFile(handle, file_map, 0, 0, 0) };
511+
let addr = unsafe { MapViewOfFile(file_mapping.0, file_map, 0, 0, 0) };
491512

492513
if addr.Value.is_null() {
493514
log_then_return!(HyperlightError::MemoryAllocationFailed(
494515
Error::last_os_error().raw_os_error()
495516
));
496517
}
518+
let view = MappedView {
519+
addr: addr.Value,
520+
len: total_size,
521+
};
497522

498523
// Set the first and last pages to be guard pages
499524

500525
let mut unused_out_old_prot_flags = PAGE_PROTECTION_FLAGS(0);
501526

502527
// If the following calls to VirtualProtect are changed make sure to update the calls to VirtualProtectEx in surrogate_process_manager.rs
503528

504-
let first_guard_page_start = addr.Value;
529+
let first_guard_page_start = view.addr;
505530
if let Err(e) = unsafe {
506531
VirtualProtect(
507532
first_guard_page_start,
@@ -513,7 +538,7 @@ impl ExclusiveSharedMemory {
513538
log_then_return!(WindowsAPIError(e.clone()));
514539
}
515540

516-
let last_guard_page_start = unsafe { addr.Value.add(total_size - PAGE_SIZE_USIZE) };
541+
let last_guard_page_start = unsafe { view.addr.add(total_size - PAGE_SIZE_USIZE) };
517542
if let Err(e) = unsafe {
518543
VirtualProtect(
519544
last_guard_page_start,
@@ -536,9 +561,7 @@ impl ExclusiveSharedMemory {
536561
// is not pointless as the lint suggests.
537562
#[allow(clippy::arc_with_non_send_sync)]
538563
region: Arc::new(HostMapping {
539-
ptr: addr.Value as *mut u8,
540-
size: total_size,
541-
handle,
564+
mapping: WindowsMapping::Anonymous { view, file_mapping },
542565
}),
543566
})
544567
}
@@ -662,7 +685,7 @@ impl ExclusiveSharedMemory {
662685
/// Gets the file handle of the shared memory region for this Sandbox
663686
#[cfg(target_os = "windows")]
664687
pub fn get_mmap_file_handle(&self) -> HANDLE {
665-
self.region.handle
688+
self.region.file_mapping_handle()
666689
}
667690

668691
/// Create a [`HostSharedMemory`] view of this region without
@@ -742,34 +765,34 @@ pub trait SharedMemory {
742765
/// need to be marked as `unsafe` because doing anything with this
743766
/// pointer itself requires `unsafe`.
744767
fn base_addr(&self) -> usize {
745-
self.region().ptr as usize + PAGE_SIZE_USIZE
768+
self.region().ptr() as usize + PAGE_SIZE_USIZE
746769
}
747770

748771
/// Return the base address of the host mapping of this region as
749772
/// a pointer. Following the general Rust philosophy, this does
750773
/// not need to be marked as `unsafe` because doing anything with
751774
/// this pointer itself requires `unsafe`.
752775
fn base_ptr(&self) -> *mut u8 {
753-
self.region().ptr.wrapping_add(PAGE_SIZE_USIZE)
776+
self.region().ptr().wrapping_add(PAGE_SIZE_USIZE)
754777
}
755778

756779
/// Return the length of usable memory contained in `self`.
757780
/// The returned size does not include the size of the surrounding
758781
/// guard pages.
759782
fn mem_size(&self) -> usize {
760-
self.region().size - 2 * PAGE_SIZE_USIZE
783+
self.region().size() - 2 * PAGE_SIZE_USIZE
761784
}
762785

763786
/// Return the raw base address of the host mapping, including the
764787
/// guard pages.
765788
fn raw_ptr(&self) -> *mut u8 {
766-
self.region().ptr
789+
self.region().ptr()
767790
}
768791

769792
/// Return the raw size of the host mapping, including the guard
770793
/// pages.
771794
fn raw_mem_size(&self) -> usize {
772-
self.region().size
795+
self.region().size()
773796
}
774797

775798
/// Extract a base address that can be mapped into a VM for this
@@ -786,9 +809,9 @@ pub trait SharedMemory {
786809
#[cfg(windows)]
787810
{
788811
super::memory_region::HostRegionBase {
789-
from_handle: self.region().handle.into(),
790-
handle_base: self.region().ptr as usize,
791-
handle_size: self.region().size,
812+
from_handle: self.region().file_mapping_handle().into(),
813+
handle_base: self.region().ptr() as usize,
814+
handle_size: self.region().size(),
792815
offset: PAGE_SIZE_USIZE,
793816
}
794817
}
@@ -829,8 +852,8 @@ pub trait SharedMemory {
829852
#[cfg(all(target_os = "linux", feature = "kvm", not(any(feature = "mshv3"))))]
830853
unsafe {
831854
let ret = libc::madvise(
832-
e.region.ptr as *mut libc::c_void,
833-
e.region.size,
855+
e.region.ptr() as *mut libc::c_void,
856+
e.region.size(),
834857
libc::MADV_DONTNEED,
835858
);
836859
if ret == 0 {
@@ -2026,6 +2049,75 @@ pub struct ReadonlySharedMemory {
20262049
unsafe impl Send for ReadonlySharedMemory {}
20272050
unsafe impl Sync for ReadonlySharedMemory {}
20282051

2052+
/// RAII guard for an `mmap` reservation. Calls `munmap` on drop.
2053+
#[cfg(target_os = "linux")]
2054+
#[derive(Debug)]
2055+
struct Mmap {
2056+
base: *mut c_void,
2057+
len: usize,
2058+
}
2059+
2060+
#[cfg(target_os = "linux")]
2061+
impl Drop for Mmap {
2062+
fn drop(&mut self) {
2063+
// SAFETY: `self.base` and `self.len` are exactly what was
2064+
// returned by the `mmap` that produced this `Mmap`, and that
2065+
// mapping has not been unmapped (we own it).
2066+
unsafe {
2067+
if libc::munmap(self.base, self.len) != 0 {
2068+
tracing::error!(
2069+
"Mmap::drop: munmap failed: {:?}",
2070+
std::io::Error::last_os_error()
2071+
);
2072+
}
2073+
}
2074+
}
2075+
}
2076+
2077+
/// RAII guard for a Win32 mapped view. Calls `UnmapViewOfFile` on drop.
2078+
#[cfg(target_os = "windows")]
2079+
#[derive(Debug)]
2080+
struct MappedView {
2081+
addr: *mut c_void,
2082+
len: usize,
2083+
}
2084+
2085+
#[cfg(target_os = "windows")]
2086+
impl Drop for MappedView {
2087+
fn drop(&mut self) {
2088+
let view = MEMORY_MAPPED_VIEW_ADDRESS { Value: self.addr };
2089+
// SAFETY: `self.addr` is the base address returned by the
2090+
// `MapViewOfFile` call that produced this `MappedView`, and
2091+
// the view has not been unmapped (we own it).
2092+
if let Err(e) = unsafe { UnmapViewOfFile(view) } {
2093+
tracing::error!(
2094+
"MappedView::drop(addr={:?}, len={}) UnmapViewOfFile failed: {:?}",
2095+
self.addr,
2096+
self.len,
2097+
e
2098+
);
2099+
}
2100+
}
2101+
}
2102+
2103+
/// Owns a Win32 file-mapping `HANDLE`. Calls `CloseHandle` on drop.
2104+
#[cfg(target_os = "windows")]
2105+
#[derive(Debug)]
2106+
struct FileMapping(HANDLE);
2107+
2108+
#[cfg(target_os = "windows")]
2109+
impl Drop for FileMapping {
2110+
fn drop(&mut self) {
2111+
// SAFETY: `self.0` is a valid HANDLE returned by
2112+
// `CreateFileMappingA` that has not been closed (we own it).
2113+
unsafe {
2114+
if let Err(e) = CloseHandle(self.0) {
2115+
tracing::error!("FileMapping::drop: CloseHandle failed: {:?}", e);
2116+
}
2117+
}
2118+
}
2119+
}
2120+
20292121
impl ReadonlySharedMemory {
20302122
pub(crate) fn from_bytes(contents: &[u8]) -> Result<Self> {
20312123
let mut anon = ExclusiveSharedMemory::new(contents.len())?;

0 commit comments

Comments
 (0)