Skip to content

Commit 369449c

Browse files
committed
Retain page table root finder in memory snapshots
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
1 parent 2150200 commit 369449c

6 files changed

Lines changed: 142 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
1616
* **Breaking:** Filesystem paths are now represented using `PathBuf`. `GuestBinary::FilePath` now stores a `PathBuf` instead of a `String`, and `MultiUseSandbox::generate_crashdump_to_dir` accepts `Into<PathBuf>` instead of `Into<String>`. Callers passing a `String` to `GuestBinary::FilePath` must convert it using `.into()`.
1717
* Deprecate `MultiUseSandbox::poisoned` in favor of `MultiUseSandbox::status().is_poisoned()`.
1818
* `MultiUseSandbox::restore` has been made more flexible and now accepts snapshots from any guest binary or memory layout when host functions are compatible.
19+
* **Breaking:** `PtRootFinder` now uses `Arc` and requires `Sync`.
1920

2021
### Removed
2122

src/hyperlight_host/src/mem/mgr.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use crate::hypervisor::regs::CommonSpecialRegisters;
3434
use crate::mem::memory_region::MemoryRegion;
3535
#[cfg(crashdump)]
3636
use crate::mem::memory_region::{CrashDumpRegion, MemoryRegionFlags, MemoryRegionType};
37+
use crate::sandbox::PtRootFinder;
3738
use crate::sandbox::snapshot::{NextAction, Snapshot};
3839
use crate::{Result, new_error};
3940

@@ -359,6 +360,7 @@ where
359360
#[cfg(target_arch = "x86_64")] msrs: Vec<crate::hypervisor::regs::MsrEntry>,
360361
next_action: NextAction,
361362
host_functions: HostFunctionDetails,
363+
pt_root_finder: Option<PtRootFinder>,
362364
) -> Result<Snapshot> {
363365
self.snapshot_count += 1;
364366
Snapshot::new(
@@ -376,6 +378,7 @@ where
376378
self.original_entrypoint,
377379
self.snapshot_count,
378380
host_functions,
381+
pt_root_finder,
379382
)
380383
}
381384
}

src/hyperlight_host/src/sandbox/initialized_multi_use.rs

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ pub struct MultiUseSandbox {
136136
///
137137
/// Returns a list of root page table GPAs to walk. If the list is
138138
/// empty, only `root_pt_gpa` is used.
139-
pub type PtRootFinder = Box<dyn Fn(&[u8], &[u8], u64) -> Vec<u64> + Send>;
139+
pub type PtRootFinder = Arc<dyn Fn(&[u8], &[u8], u64) -> Vec<u64> + Send + Sync>;
140140

141141
impl MultiUseSandbox {
142142
fn ensure_usable(&self) -> Result<()> {
@@ -180,8 +180,12 @@ impl MultiUseSandbox {
180180
/// Set a callback that discovers page table roots from guest memory.
181181
/// The callback receives (snapshot_mem, scratch_mem, cr3) and returns
182182
/// the list of root GPAs to walk during snapshot creation.
183+
///
184+
/// In-memory snapshots retain the finder across restore. The finder is not
185+
/// serialized.
183186
pub fn set_pt_root_finder(&mut self, finder: PtRootFinder) {
184187
self.pt_root_finder = Some(finder);
188+
self.snapshot = None;
185189
}
186190

187191
/// Create a `MultiUseSandbox` directly from a [`Snapshot`],
@@ -365,13 +369,14 @@ impl MultiUseSandbox {
365369
#[cfg(gdb)]
366370
let dbg_mem_wrapper = Arc::new(Mutex::new(hshm.clone()));
367371

368-
let sbox = MultiUseSandbox::from_uninit(
372+
let mut sbox = MultiUseSandbox::from_uninit(
369373
host_funcs,
370374
hshm,
371375
vm,
372376
#[cfg(gdb)]
373377
dbg_mem_wrapper,
374378
);
379+
sbox.pt_root_finder = snapshot.pt_root_finder().cloned();
375380
Ok(sbox)
376381
}
377382

@@ -462,6 +467,7 @@ impl MultiUseSandbox {
462467
msrs,
463468
next_action,
464469
host_functions,
470+
self.pt_root_finder.clone(),
465471
)?;
466472
let snapshot = Arc::new(memory_snapshot);
467473
self.snapshot = Some(snapshot.clone());
@@ -682,7 +688,7 @@ impl MultiUseSandbox {
682688
self.vm.clear_crashdump_binary_path();
683689
}
684690

685-
self.pt_root_finder = None;
691+
self.pt_root_finder = snapshot.pt_root_finder().cloned();
686692

687693
// The restored snapshot is now our most current snapshot
688694
self.snapshot = Some(snapshot.clone());
@@ -1249,6 +1255,7 @@ fn warn_on_layout_override(
12491255

12501256
#[cfg(test)]
12511257
mod tests {
1258+
use std::sync::atomic::{AtomicUsize, Ordering};
12521259
use std::sync::{Arc, Barrier};
12531260
use std::thread;
12541261

@@ -1262,6 +1269,7 @@ mod tests {
12621269
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags, MemoryRegionType};
12631270
use crate::mem::shared_mem::{ExclusiveSharedMemory, GuestSharedMemory, SharedMemory as _};
12641271
use crate::sandbox::SandboxConfiguration;
1272+
use crate::sandbox::snapshot::Snapshot;
12651273
use crate::sandbox::uninitialized::{GuestBlob, GuestEnvironment};
12661274
use crate::{
12671275
GuestBinary, HyperlightError, MultiUseSandbox, Result, SandboxStatus, UninitializedSandbox,
@@ -1282,6 +1290,23 @@ mod tests {
12821290
assert!(SandboxStatus::Unrecoverable.is_unrecoverable());
12831291
}
12841292

1293+
trait AmbiguousIfSync<Marker> {
1294+
fn assert_not_sync() {}
1295+
}
1296+
1297+
impl<T: ?Sized> AmbiguousIfSync<()> for T {}
1298+
impl<T: ?Sized + Sync> AmbiguousIfSync<u8> for T {}
1299+
1300+
#[test]
1301+
fn snapshot_and_sandbox_thread_safety() {
1302+
fn assert_send<T: Send>() {}
1303+
fn assert_send_sync<T: Send + Sync>() {}
1304+
1305+
assert_send::<MultiUseSandbox>();
1306+
let _ = <MultiUseSandbox as AmbiguousIfSync<_>>::assert_not_sync;
1307+
assert_send_sync::<Snapshot>();
1308+
}
1309+
12851310
#[test]
12861311
fn poison() {
12871312
let mut sbox: MultiUseSandbox = {
@@ -2590,6 +2615,8 @@ mod tests {
25902615
.unwrap()
25912616
.evolve()
25922617
.unwrap();
2618+
let source_finder: crate::sandbox::PtRootFinder = Arc::new(|_, _, root| vec![root]);
2619+
source.set_pt_root_finder(source_finder.clone());
25932620
let mut target =
25942621
UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None)
25952622
.unwrap()
@@ -2598,8 +2625,7 @@ mod tests {
25982625

25992626
assert_eq!(source.call::<i32>("StackAllocate", 256i32).unwrap(), 256);
26002627
assert_eq!(target.call::<i32>("AddToStatic", 17i32).unwrap(), 17);
2601-
target.set_pt_root_finder(Box::new(|_, _, root| vec![root]));
2602-
assert!(target.pt_root_finder.is_some());
2628+
target.set_pt_root_finder(Arc::new(|_, _, _| Vec::new()));
26032629

26042630
assert_ne!(
26052631
source.mem_mgr.layout.code_size(),
@@ -2616,7 +2642,10 @@ mod tests {
26162642

26172643
let snapshot = source.snapshot().unwrap();
26182644
target.restore(snapshot).unwrap();
2619-
assert!(target.pt_root_finder.is_none());
2645+
assert!(Arc::ptr_eq(
2646+
target.pt_root_finder.as_ref().unwrap(),
2647+
&source_finder
2648+
));
26202649
assert_eq!(target.call::<i32>("StackAllocate", 512i32).unwrap(), 512);
26212650
assert!(matches!(
26222651
target.call::<i32>("GetStatic", ()),
@@ -2627,6 +2656,68 @@ mod tests {
26272656
));
26282657
}
26292658

2659+
#[test]
2660+
fn snapshot_restore_clears_absent_pt_root_finder() {
2661+
let path = simple_guest_as_pathbuf();
2662+
let mut source = UninitializedSandbox::new(GuestBinary::FilePath(path), None)
2663+
.unwrap()
2664+
.evolve()
2665+
.unwrap();
2666+
let snapshot = source.snapshot().unwrap();
2667+
assert!(snapshot.pt_root_finder().is_none());
2668+
2669+
let path = simple_guest_as_pathbuf();
2670+
let mut target = UninitializedSandbox::new(GuestBinary::FilePath(path), None)
2671+
.unwrap()
2672+
.evolve()
2673+
.unwrap();
2674+
target.set_pt_root_finder(Arc::new(|_, _, root| vec![root]));
2675+
2676+
target.restore(snapshot).unwrap();
2677+
assert!(target.pt_root_finder.is_none());
2678+
}
2679+
2680+
#[test]
2681+
fn snapshot_restore_uses_retained_pt_root_finder() {
2682+
let source_calls = Arc::new(AtomicUsize::new(0));
2683+
let source_calls_in_finder = source_calls.clone();
2684+
let source_finder: crate::sandbox::PtRootFinder = Arc::new(move |_, _, _| {
2685+
source_calls_in_finder.fetch_add(1, Ordering::Relaxed);
2686+
Vec::new()
2687+
});
2688+
let path = simple_guest_as_pathbuf();
2689+
let mut source = UninitializedSandbox::new(GuestBinary::FilePath(path), None)
2690+
.unwrap()
2691+
.evolve()
2692+
.unwrap();
2693+
source.set_pt_root_finder(source_finder);
2694+
let snapshot = source.snapshot().unwrap();
2695+
2696+
let target_calls = Arc::new(AtomicUsize::new(0));
2697+
let target_calls_in_finder = target_calls.clone();
2698+
let target_finder: crate::sandbox::PtRootFinder = Arc::new(move |_, _, root| {
2699+
target_calls_in_finder.fetch_add(1, Ordering::Relaxed);
2700+
vec![root]
2701+
});
2702+
let path = simple_guest_as_pathbuf();
2703+
let mut target = UninitializedSandbox::new(GuestBinary::FilePath(path), None)
2704+
.unwrap()
2705+
.evolve()
2706+
.unwrap();
2707+
target.set_pt_root_finder(target_finder);
2708+
target.restore(snapshot).unwrap();
2709+
2710+
let source_calls_before = source_calls.load(Ordering::Relaxed);
2711+
target.call::<i32>("GetStatic", ()).unwrap();
2712+
target.snapshot().unwrap();
2713+
2714+
assert_eq!(
2715+
source_calls.load(Ordering::Relaxed),
2716+
source_calls_before + 1
2717+
);
2718+
assert_eq!(target_calls.load(Ordering::Relaxed), 0);
2719+
}
2720+
26302721
#[test]
26312722
fn snapshot_restore_replaces_c_guest_with_rust_guest() {
26322723
let mut source =

src/hyperlight_host/src/sandbox/snapshot/file/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,10 @@ impl Snapshot {
334334
/// guest is running. Any release that breaks the format is called
335335
/// out in the Hyperlight changelog.
336336
///
337+
/// A [`PtRootFinder`](crate::sandbox::PtRootFinder) configured with
338+
/// [`set_pt_root_finder`](crate::MultiUseSandbox::set_pt_root_finder) is not
339+
/// serialized. Set it again on any sandbox created from the loaded snapshot.
340+
///
337341
/// # Examples
338342
///
339343
/// ```no_run
@@ -668,6 +672,11 @@ impl Snapshot {
668672
/// guest is running. Any release that breaks the format is called
669673
/// out in the Hyperlight changelog.
670674
///
675+
/// If the source sandbox used
676+
/// [`MultiUseSandbox::set_pt_root_finder`](crate::MultiUseSandbox::set_pt_root_finder),
677+
/// set the finder again on the sandbox created from this snapshot. The finder
678+
/// is not serialized.
679+
///
671680
/// # Verification
672681
///
673682
/// This method does not check the manifest, config, or snapshot
@@ -909,6 +918,7 @@ impl Snapshot {
909918
original_entrypoint: cfg.original_entrypoint_addr,
910919
snapshot_generation,
911920
host_functions,
921+
pt_root_finder: None,
912922
})
913923
}
914924
}

src/hyperlight_host/src/sandbox/snapshot/file_tests.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use sha2::{Digest as _, Sha256};
2626

2727
use crate::func::Registerable;
2828
use crate::mem::layout::SandboxMemoryLayout;
29+
use crate::sandbox::PtRootFinder;
2930
use crate::sandbox::snapshot::{OciDigest, OciReference, OciTag, Snapshot};
3031
use crate::{GuestBinary, HostFunctions, MultiUseSandbox, UninitializedSandbox};
3132

@@ -95,9 +96,21 @@ fn find_snapshot_blob(oci_dir: &std::path::Path) -> std::path::PathBuf {
9596

9697
#[test]
9798
fn from_snapshot_already_initialized_in_memory() {
98-
let snapshot = create_snapshot();
99+
let mut source = create_test_sandbox();
100+
let initial_snapshot = source.snapshot().unwrap();
101+
let finder: PtRootFinder = Arc::new(|_, _, root| vec![root]);
102+
source.set_pt_root_finder(finder.clone());
103+
let snapshot = source.snapshot().unwrap();
104+
assert!(!Arc::ptr_eq(&initial_snapshot, &snapshot));
105+
assert!(Arc::ptr_eq(snapshot.pt_root_finder().unwrap(), &finder));
106+
99107
let mut sbox2 =
100108
MultiUseSandbox::from_snapshot(snapshot, HostFunctions::default(), None).unwrap();
109+
let restored_snapshot = sbox2.snapshot().unwrap();
110+
assert!(Arc::ptr_eq(
111+
restored_snapshot.pt_root_finder().unwrap(),
112+
&finder
113+
));
101114
let result: i32 = sbox2.call("GetStatic", ()).unwrap();
102115
assert_eq!(result, 0);
103116
}
@@ -119,7 +132,9 @@ fn from_snapshot_in_memory_pre_init() {
119132

120133
#[test]
121134
fn round_trip_save_load_call() {
122-
let snapshot = create_snapshot();
135+
let mut source = create_test_sandbox();
136+
source.set_pt_root_finder(Arc::new(|_, _, root| vec![root]));
137+
let snapshot = source.snapshot().unwrap();
123138

124139
let dir = tempfile::tempdir().unwrap();
125140
let oci = dir.path().join("snap");
@@ -128,6 +143,7 @@ fn round_trip_save_load_call() {
128143
.unwrap();
129144

130145
let loaded = Snapshot::checked_load(&oci, OciTag::new("latest").unwrap()).unwrap();
146+
assert!(loaded.pt_root_finder().is_none());
131147
let mut sbox2 =
132148
MultiUseSandbox::from_snapshot(Arc::new(loaded), HostFunctions::default(), None).unwrap();
133149

src/hyperlight_host/src/sandbox/snapshot/mod.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ use crate::mem::layout::SandboxMemoryLayout;
3939
use crate::mem::memory_region::{GuestMemoryRegion, MemoryRegion, MemoryRegionFlags};
4040
use crate::mem::mgr::{GuestPageTableBuffer, SnapshotSharedMemory};
4141
use crate::mem::shared_mem::{ReadonlySharedMemory, SharedMemory};
42-
use crate::sandbox::SandboxConfiguration;
4342
use crate::sandbox::uninitialized::{GuestBinary, GuestEnvironment};
43+
use crate::sandbox::{PtRootFinder, SandboxConfiguration};
4444

4545
const PTE_SIZE: usize = size_of::<vmem::PageTableEntry>();
4646

@@ -123,6 +123,9 @@ pub struct Snapshot {
123123
/// `HostFunctions` set that is missing required functions or
124124
/// has mismatched signatures.
125125
host_functions: HostFunctionDetails,
126+
127+
/// Runtime-only page-table root finder retained by in-memory snapshots.
128+
pt_root_finder: Option<PtRootFinder>,
126129
}
127130
impl core::convert::AsRef<Snapshot> for Snapshot {
128131
fn as_ref(&self) -> &Self {
@@ -406,6 +409,7 @@ impl Snapshot {
406409
host_functions: HostFunctionDetails {
407410
host_functions: None,
408411
},
412+
pt_root_finder: None,
409413
})
410414
}
411415

@@ -432,6 +436,7 @@ impl Snapshot {
432436
original_entrypoint: u64,
433437
snapshot_generation: u64,
434438
host_functions: HostFunctionDetails,
439+
pt_root_finder: Option<PtRootFinder>,
435440
) -> Result<Self> {
436441
let mut phys_seen = HashMap::<u64, usize>::new();
437442
let scratch_gva = scratch_base_gva(layout.get_scratch_size());
@@ -588,6 +593,7 @@ impl Snapshot {
588593
original_entrypoint,
589594
snapshot_generation,
590595
host_functions,
596+
pt_root_finder,
591597
})
592598
}
593599

@@ -596,6 +602,10 @@ impl Snapshot {
596602
self.snapshot_generation
597603
}
598604

605+
pub(crate) fn pt_root_finder(&self) -> Option<&PtRootFinder> {
606+
self.pt_root_finder.as_ref()
607+
}
608+
599609
/// Return the main memory contents of the snapshot
600610
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
601611
pub(crate) fn memory(&self) -> &ReadonlySharedMemory {
@@ -785,6 +795,7 @@ mod tests {
785795
0,
786796
1,
787797
HostFunctionDetails::default(),
798+
None,
788799
)
789800
.unwrap();
790801

@@ -805,6 +816,7 @@ mod tests {
805816
0,
806817
2,
807818
HostFunctionDetails::default(),
819+
None,
808820
)
809821
.unwrap();
810822

0 commit comments

Comments
 (0)