Skip to content

Commit 13e115c

Browse files
committed
[hyperlight_host] Allow mapping a host memory region into a guest
Signed-off-by: Lucy Menon <[email protected]>
1 parent 30b24de commit 13e115c

File tree

8 files changed

+200
-12
lines changed

8 files changed

+200
-12
lines changed

src/hyperlight_host/src/func/call_ctx.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
use tracing::{Span, instrument};
1818

1919
use super::{ParameterTuple, SupportedReturnType};
20+
use crate::mem::memory_region::MemoryRegion;
2021
use crate::sandbox::Callable;
2122
use crate::{MultiUseSandbox, Result};
2223
/// A context for calling guest functions.
@@ -70,6 +71,30 @@ impl MultiUseGuestCallContext {
7071
pub(crate) fn finish_no_reset(self) -> MultiUseSandbox {
7172
self.sbox
7273
}
74+
75+
/// Map a region of host memory into the sandbox.
76+
///
77+
/// Depending on the host platform, there are likely alignment
78+
/// requirements of at least one page for base and len.
79+
///
80+
/// `rgn.region_type` is ignored, since guest PTEs are not created
81+
/// for the new memory.
82+
///
83+
/// # Safety
84+
/// It is the caller's responsibility to ensure that the host side
85+
/// of the region remains intact and is not written to until this
86+
/// mapping is removed, either due to the destruction of the
87+
/// sandbox or due to a state rollback
88+
pub unsafe fn map_region(&mut self, rgn: &MemoryRegion) -> Result<()> {
89+
unsafe { self.sbox.map_region(rgn) }
90+
}
91+
92+
/// Map the contents of a file into the guest at a particular address
93+
///
94+
/// Returns the length of the mapping
95+
pub fn map_file_cow(&mut self, fp: &std::path::Path, guest_base: u64) -> Result<u64> {
96+
self.sbox.map_file_cow(fp, guest_base)
97+
}
7398
}
7499

75100
impl Callable for MultiUseGuestCallContext {

src/hyperlight_host/src/hypervisor/hyperv_linux.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ pub(crate) fn is_hypervisor_present() -> bool {
297297
/// called the Microsoft Hypervisor (MSHV)
298298
pub(crate) struct HypervLinuxDriver {
299299
_mshv: Mshv,
300+
page_size: usize,
300301
vm_fd: VmFd,
301302
vcpu_fd: VcpuFd,
302303
entrypoint: u64,
@@ -424,6 +425,7 @@ impl HypervLinuxDriver {
424425
#[allow(unused_mut)]
425426
let mut hv = Self {
426427
_mshv: mshv,
428+
page_size: 0,
427429
vm_fd,
428430
vcpu_fd,
429431
mem_regions,
@@ -525,6 +527,8 @@ impl Hypervisor for HypervLinuxDriver {
525527
max_guest_log_level: Option<LevelFilter>,
526528
#[cfg(gdb)] dbg_mem_access_fn: DbgMemAccessHandlerWrapper,
527529
) -> Result<()> {
530+
self.page_size = page_size as usize;
531+
528532
let max_guest_log_level: u64 = match max_guest_log_level {
529533
Some(level) => level as u64,
530534
None => self.get_max_log_level().into(),
@@ -556,6 +560,37 @@ impl Hypervisor for HypervLinuxDriver {
556560
Ok(())
557561
}
558562

563+
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
564+
unsafe fn map_region(&mut self, rgn: &MemoryRegion) -> Result<()> {
565+
if [
566+
rgn.guest_region.start,
567+
rgn.guest_region.end,
568+
rgn.host_region.start,
569+
rgn.host_region.end,
570+
]
571+
.iter()
572+
.any(|x| x % self.page_size != 0)
573+
{
574+
log_then_return!("region is not page-aligned");
575+
}
576+
let mshv_region: mshv_user_mem_region = rgn.to_owned().into();
577+
self.vm_fd.map_user_memory(mshv_region)?;
578+
self.mem_regions.push(rgn.to_owned());
579+
Ok(())
580+
}
581+
582+
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
583+
unsafe fn unmap_regions(&mut self, n: u64) -> Result<()> {
584+
for rgn in self
585+
.mem_regions
586+
.split_off(self.mem_regions.len() - n as usize)
587+
{
588+
let mshv_region: mshv_user_mem_region = rgn.to_owned().into();
589+
self.vm_fd.unmap_user_memory(mshv_region)?;
590+
}
591+
Ok(())
592+
}
593+
559594
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
560595
fn dispatch_call_from_host(
561596
&mut self,

src/hyperlight_host/src/hypervisor/hyperv_windows.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,16 @@ impl Hypervisor for HypervWindowsDriver {
606606
Ok(())
607607
}
608608

609+
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
610+
unsafe fn map_region(&mut self, _rgn: MemoryRegion) -> Result<()> {
611+
log_then_return!("Mapping host memory into the guest not yet supported on this platform");
612+
}
613+
614+
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
615+
unsafe fn unmap_regions(&mut self, _n: u64) -> Result<()> {
616+
log_then_return!("Mapping host memory into the guest not yet supported on this platform");
617+
}
618+
609619
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
610620
fn dispatch_call_from_host(
611621
&mut self,

src/hyperlight_host/src/hypervisor/kvm.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,16 @@ impl Hypervisor for KVMDriver {
493493
Ok(())
494494
}
495495

496+
#[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");
499+
}
500+
501+
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
502+
unsafe fn unmap_regions(&mut self, _n: u64) -> Result<()> {
503+
log_then_return!("Mapping host memory into the guest not yet supported on this platform");
504+
}
505+
496506
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
497507
fn dispatch_call_from_host(
498508
&mut self,

src/hyperlight_host/src/hypervisor/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,15 @@ pub(crate) trait Hypervisor: Debug + Sync + Send {
132132
#[cfg(gdb)] dbg_mem_access_fn: DbgMemAccessHandlerWrapper,
133133
) -> Result<()>;
134134

135+
/// Map a region of host memory into the sandbox.
136+
///
137+
/// Depending on the host platform, there are likely alignment
138+
/// requirements of at least one page for base and len.
139+
unsafe fn map_region(&mut self, rgn: &MemoryRegion) -> Result<()>;
140+
141+
/// Unmap the most recent `n` regions mapped by `map_region`
142+
unsafe fn unmap_regions(&mut self, n: u64) -> Result<()>;
143+
135144
/// Dispatch a call from the host to the guest using the given pointer
136145
/// to the dispatch function _in the guest's address space_.
137146
///

src/hyperlight_host/src/mem/mgr.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ pub(crate) struct SandboxMemoryManager<S> {
7373
pub(crate) load_addr: RawPtr,
7474
/// Offset for the execution entrypoint from `load_addr`
7575
pub(crate) entrypoint_offset: Offset,
76+
/// How many memory regions were mapped after sandbox creation
77+
pub(crate) mapped_rgns: u64,
7678
/// A vector of memory snapshots that can be used to save and restore the state of the memory
7779
/// This is used by the Rust Sandbox implementation (rather than the mem_snapshot field above which only exists to support current C API)
7880
snapshots: Arc<Mutex<Vec<SharedMemorySnapshot>>>,
@@ -95,6 +97,7 @@ where
9597
shared_mem,
9698
load_addr,
9799
entrypoint_offset,
100+
mapped_rgns: 0,
98101
snapshots: Arc::new(Mutex::new(Vec::new())),
99102
}
100103
}
@@ -265,7 +268,7 @@ where
265268
/// this function will create a memory snapshot and push it onto the stack of snapshots
266269
/// It should be used when you want to save the state of the memory, for example, when evolving a sandbox to a new state
267270
pub(crate) fn push_state(&mut self) -> Result<()> {
268-
let snapshot = SharedMemorySnapshot::new(&mut self.shared_mem)?;
271+
let snapshot = SharedMemorySnapshot::new(&mut self.shared_mem, self.mapped_rgns)?;
269272
self.snapshots
270273
.try_lock()
271274
.map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
@@ -277,7 +280,11 @@ where
277280
/// off the stack
278281
/// It should be used when you want to restore the state of the memory to a previous state but still want to
279282
/// retain that state, for example after calling a function in the guest
280-
pub(crate) fn restore_state_from_last_snapshot(&mut self) -> Result<()> {
283+
///
284+
/// Returns the number of memory regions mapped into the sandbox
285+
/// that need to be unmapped in order for the restore to be
286+
/// completed.
287+
pub(crate) fn restore_state_from_last_snapshot(&mut self) -> Result<u64> {
281288
let mut snapshots = self
282289
.snapshots
283290
.try_lock()
@@ -288,13 +295,15 @@ where
288295
}
289296
#[allow(clippy::unwrap_used)] // We know that last is not None because we checked it above
290297
let snapshot = last.unwrap();
291-
snapshot.restore_from_snapshot(&mut self.shared_mem)
298+
let old_rgns = self.mapped_rgns;
299+
self.mapped_rgns = snapshot.restore_from_snapshot(&mut self.shared_mem)?;
300+
Ok(old_rgns - self.mapped_rgns)
292301
}
293302

294303
/// this function pops the last snapshot off the stack and restores the memory to the previous state
295304
/// It should be used when you want to restore the state of the memory to a previous state and do not need to retain that state
296305
/// for example when devolving a sandbox to a previous state.
297-
pub(crate) fn pop_and_restore_state_from_snapshot(&mut self) -> Result<()> {
306+
pub(crate) fn pop_and_restore_state_from_snapshot(&mut self) -> Result<u64> {
298307
let last = self
299308
.snapshots
300309
.try_lock()
@@ -430,13 +439,15 @@ impl SandboxMemoryManager<ExclusiveSharedMemory> {
430439
layout: self.layout,
431440
load_addr: self.load_addr.clone(),
432441
entrypoint_offset: self.entrypoint_offset,
442+
mapped_rgns: 0,
433443
snapshots: Arc::new(Mutex::new(Vec::new())),
434444
},
435445
SandboxMemoryManager {
436446
shared_mem: gshm,
437447
layout: self.layout,
438448
load_addr: self.load_addr.clone(),
439449
entrypoint_offset: self.entrypoint_offset,
450+
mapped_rgns: 0,
440451
snapshots: Arc::new(Mutex::new(Vec::new())),
441452
},
442453
)

src/hyperlight_host/src/mem/shared_mem_snapshot.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,21 @@ use crate::Result;
2424
#[derive(Clone)]
2525
pub(super) struct SharedMemorySnapshot {
2626
snapshot: Vec<u8>,
27+
/// How many non-main-RAM regions were mapped when this snapshot was taken?
28+
mapped_rgns: u64,
2729
}
2830

2931
impl SharedMemorySnapshot {
3032
/// Take a snapshot of the memory in `shared_mem`, then create a new
3133
/// instance of `Self` with the snapshot stored therein.
3234
#[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
33-
pub(super) fn new<S: SharedMemory>(shared_mem: &mut S) -> Result<Self> {
35+
pub(super) fn new<S: SharedMemory>(shared_mem: &mut S, mapped_rgns: u64) -> Result<Self> {
3436
// TODO: Track dirty pages instead of copying entire memory
3537
let snapshot = shared_mem.with_exclusivity(|e| e.copy_all_to_vec())??;
36-
Ok(Self { snapshot })
38+
Ok(Self {
39+
snapshot,
40+
mapped_rgns,
41+
})
3742
}
3843

3944
/// Take another snapshot of the internally-stored `SharedMemory`,
@@ -51,8 +56,9 @@ impl SharedMemorySnapshot {
5156
pub(super) fn restore_from_snapshot<S: SharedMemory>(
5257
&mut self,
5358
shared_mem: &mut S,
54-
) -> Result<()> {
55-
shared_mem.with_exclusivity(|e| e.copy_from_slice(self.snapshot.as_slice(), 0))?
59+
) -> Result<u64> {
60+
shared_mem.with_exclusivity(|e| e.copy_from_slice(self.snapshot.as_slice(), 0))??;
61+
Ok(self.mapped_rgns)
5662
}
5763
}
5864

@@ -69,7 +75,7 @@ mod tests {
6975
let data2 = data1.iter().map(|b| b + 1).collect::<Vec<u8>>();
7076
let mut gm = ExclusiveSharedMemory::new(PAGE_SIZE_USIZE).unwrap();
7177
gm.copy_from_slice(data1.as_slice(), 0).unwrap();
72-
let mut snap = super::SharedMemorySnapshot::new(&mut gm).unwrap();
78+
let mut snap = super::SharedMemorySnapshot::new(&mut gm, 0).unwrap();
7379
{
7480
// after the first snapshot is taken, make sure gm has the equivalent
7581
// of data1

src/hyperlight_host/src/sandbox/initialized_multi_use.rs

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
use std::mem::MaybeUninit;
18+
use std::os::unix::ffi::OsStrExt;
19+
use std::path::Path;
1720
use std::sync::{Arc, Mutex};
1821

1922
use hyperlight_common::flatbuffer_wrappers::function_call::{FunctionCall, FunctionCallType};
@@ -31,12 +34,13 @@ use crate::func::{ParameterTuple, SupportedReturnType};
3134
use crate::hypervisor::handlers::DbgMemAccessHandlerWrapper;
3235
use crate::hypervisor::handlers::{MemAccessHandlerCaller, OutBHandlerCaller};
3336
use crate::hypervisor::{Hypervisor, InterruptHandle};
37+
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags, MemoryRegionType};
3438
use crate::mem::ptr::RawPtr;
3539
use crate::mem::shared_mem::HostSharedMemory;
3640
use crate::metrics::maybe_time_and_emit_guest_call;
3741
use crate::sandbox_state::sandbox::{DevolvableSandbox, EvolvableSandbox, Sandbox};
3842
use crate::sandbox_state::transition::{MultiUseContextCallback, Noop};
39-
use crate::{HyperlightError, Result};
43+
use crate::{HyperlightError, Result, log_then_return};
4044

4145
/// A sandbox that supports being used Multiple times.
4246
/// The implication of being used multiple times is two-fold:
@@ -173,6 +177,80 @@ impl MultiUseSandbox {
173177
})
174178
}
175179

180+
/// Map a region of host memory into the sandbox.
181+
///
182+
/// Depending on the host platform, there are likely alignment
183+
/// requirements of at least one page for base and len.
184+
///
185+
/// `rgn.region_type` is ignored, since guest PTEs are not created
186+
/// for the new memory.
187+
///
188+
/// It is the caller's responsibility to ensure that the host side
189+
/// of the region remains intact and is not written to until this
190+
/// mapping is removed, either due to the destruction of the
191+
/// sandbox or due to a state rollback
192+
#[instrument(err(Debug), skip(self, rgn), parent = Span::current())]
193+
pub unsafe fn map_region(&mut self, rgn: &MemoryRegion) -> Result<()> {
194+
if rgn.flags.contains(MemoryRegionFlags::STACK_GUARD) {
195+
// Stack guard pages are an internal implementation detail
196+
// (which really should be moved into the guest)
197+
log_then_return!("Cannot map host memory as a stack guard page");
198+
}
199+
if rgn.flags.contains(MemoryRegionFlags::WRITE) {
200+
// TODO: Implement support for writable mappings, which
201+
// need to be registered with the memory manager so that
202+
// writes can be rolled back when necessary.
203+
log_then_return!("TODO: Writable mappings not yet supported");
204+
}
205+
unsafe { self.vm.map_region(rgn) }?;
206+
self.mem_mgr.unwrap_mgr_mut().mapped_rgns += 1;
207+
Ok(())
208+
}
209+
210+
/// Map the contents of a file into the guest at a particular address
211+
///
212+
/// Returns the length of the mapping
213+
#[instrument(err(Debug), skip(self, fp, guest_base), parent = Span::current())]
214+
pub(crate) fn map_file_cow(&mut self, fp: &Path, guest_base: u64) -> Result<u64> {
215+
#[cfg(windows)]
216+
log_then_return!("mmap'ing a file into the guest is not yet supported on Windows");
217+
unsafe {
218+
let fd = libc::open(
219+
std::ffi::CString::new(fp.as_os_str().as_bytes())?.as_ptr(),
220+
libc::O_RDWR,
221+
);
222+
if fd < 0 {
223+
log_then_return!("open error: {:?}", std::io::Error::last_os_error());
224+
}
225+
let mut st = MaybeUninit::<libc::stat>::uninit();
226+
libc::fstat(fd, st.as_mut_ptr());
227+
let st = st.assume_init();
228+
let page_size = page_size::get();
229+
let size = (st.st_size as usize).div_ceil(page_size) * page_size;
230+
let base = libc::mmap(
231+
std::ptr::null_mut(),
232+
size,
233+
libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC,
234+
libc::MAP_PRIVATE,
235+
fd,
236+
0,
237+
);
238+
if base == libc::MAP_FAILED {
239+
log_then_return!("mmap error: {:?}", std::io::Error::last_os_error());
240+
}
241+
libc::close(fd);
242+
243+
self.map_region(&MemoryRegion {
244+
host_region: base as usize..base.wrapping_add(size) as usize,
245+
guest_region: guest_base as usize..guest_base as usize + size,
246+
flags: MemoryRegionFlags::READ | MemoryRegionFlags::EXECUTE,
247+
region_type: MemoryRegionType::Heap,
248+
})?;
249+
250+
Ok(size as u64)
251+
}
252+
}
253+
176254
/// This function is kept here for fuzz testing the parameter and return types
177255
#[cfg(feature = "fuzzing")]
178256
#[instrument(err(Debug), skip(self, args), parent = Span::current())]
@@ -193,7 +271,9 @@ impl MultiUseSandbox {
193271
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
194272
pub(crate) fn restore_state(&mut self) -> Result<()> {
195273
let mem_mgr = self.mem_mgr.unwrap_mgr_mut();
196-
mem_mgr.restore_state_from_last_snapshot()
274+
let rgns_to_unmap = mem_mgr.restore_state_from_last_snapshot()?;
275+
unsafe { self.vm.unmap_regions(rgns_to_unmap)? };
276+
Ok(())
197277
}
198278

199279
pub(crate) fn call_guest_function_by_name_no_reset(
@@ -275,9 +355,11 @@ impl DevolvableSandbox<MultiUseSandbox, MultiUseSandbox, Noop<MultiUseSandbox, M
275355
/// The devolve can be used to return the MultiUseSandbox to the state before the code was loaded. Thus avoiding initialisation overhead
276356
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
277357
fn devolve(mut self, _tsn: Noop<MultiUseSandbox, MultiUseSandbox>) -> Result<MultiUseSandbox> {
278-
self.mem_mgr
358+
let rgns_to_unmap = self
359+
.mem_mgr
279360
.unwrap_mgr_mut()
280361
.pop_and_restore_state_from_snapshot()?;
362+
unsafe { self.vm.unmap_regions(rgns_to_unmap)? };
281363
Ok(self)
282364
}
283365
}

0 commit comments

Comments
 (0)