Skip to content

Commit 39e2ed7

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

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
Certain fixed guest addresses were changed on AArch64 to more easily
2122
accommodate 16k pages without wasting memory. Snapshots taken from

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

@@ -308,6 +309,7 @@ where
308309
#[cfg(target_arch = "x86_64")] msrs: Vec<crate::hypervisor::regs::MsrEntry>,
309310
next_action: NextAction,
310311
host_functions: HostFunctionDetails,
312+
pt_root_finder: Option<PtRootFinder>,
311313
) -> Result<Snapshot> {
312314
self.snapshot_count += 1;
313315
Snapshot::new(
@@ -325,6 +327,7 @@ where
325327
self.original_entrypoint,
326328
self.snapshot_count,
327329
host_functions,
330+
pt_root_finder,
328331
)
329332
}
330333
}

src/hyperlight_host/src/sandbox/initialized_multi_use.rs

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

121121
impl MultiUseSandbox {
122122
fn ensure_usable(&self) -> Result<()> {
@@ -157,8 +157,12 @@ impl MultiUseSandbox {
157157
/// Set a callback that discovers page table roots from guest memory.
158158
/// The callback receives (snapshot_mem, scratch_mem, cr3) and returns
159159
/// the list of root GPAs to walk during snapshot creation.
160+
///
161+
/// In-memory snapshots retain the finder across restore. The finder is not
162+
/// serialized.
160163
pub fn set_pt_root_finder(&mut self, finder: PtRootFinder) {
161164
self.pt_root_finder = Some(finder);
165+
self.snapshot = None;
162166
}
163167

164168
/// Create a `MultiUseSandbox` directly from a [`Snapshot`],
@@ -332,7 +336,8 @@ impl MultiUseSandbox {
332336
})?;
333337
}
334338

335-
let sbox = MultiUseSandbox::from_uninit(host_funcs, hshm, vm);
339+
let mut sbox = MultiUseSandbox::from_uninit(host_funcs, hshm, vm);
340+
sbox.pt_root_finder = snapshot.pt_root_finder().cloned();
336341
Ok(sbox)
337342
}
338343

@@ -424,6 +429,7 @@ impl MultiUseSandbox {
424429
msrs,
425430
next_action,
426431
host_functions,
432+
self.pt_root_finder.clone(),
427433
)?;
428434
let snapshot = Arc::new(memory_snapshot);
429435
self.snapshot = Some(snapshot.clone());
@@ -619,7 +625,7 @@ impl MultiUseSandbox {
619625

620626
self.mem_mgr
621627
.request_libc_rng_reseed(rand::random::<u32>())?;
622-
self.pt_root_finder = None;
628+
self.pt_root_finder = snapshot.pt_root_finder().cloned();
623629

624630
// The restored snapshot is now our most current snapshot
625631
self.snapshot = Some(snapshot.clone());
@@ -1189,6 +1195,7 @@ fn warn_on_layout_override(
11891195

11901196
#[cfg(test)]
11911197
mod tests {
1198+
use std::sync::atomic::{AtomicUsize, Ordering};
11921199
use std::sync::{Arc, Barrier};
11931200
use std::thread;
11941201

@@ -1202,6 +1209,7 @@ mod tests {
12021209
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags, MemoryRegionType};
12031210
use crate::mem::shared_mem::{ExclusiveSharedMemory, GuestSharedMemory, SharedMemory as _};
12041211
use crate::sandbox::SandboxConfiguration;
1212+
use crate::sandbox::snapshot::Snapshot;
12051213
use crate::sandbox::uninitialized::{GuestBlob, GuestEnvironment};
12061214
use crate::{
12071215
GuestBinary, HyperlightError, MultiUseSandbox, Result, SandboxStatus, UninitializedSandbox,
@@ -1222,6 +1230,23 @@ mod tests {
12221230
assert!(SandboxStatus::Unrecoverable.is_unrecoverable());
12231231
}
12241232

1233+
trait AmbiguousIfSync<Marker> {
1234+
fn assert_not_sync() {}
1235+
}
1236+
1237+
impl<T: ?Sized> AmbiguousIfSync<()> for T {}
1238+
impl<T: ?Sized + Sync> AmbiguousIfSync<u8> for T {}
1239+
1240+
#[test]
1241+
fn snapshot_and_sandbox_thread_safety() {
1242+
fn assert_send<T: Send>() {}
1243+
fn assert_send_sync<T: Send + Sync>() {}
1244+
1245+
assert_send::<MultiUseSandbox>();
1246+
let _ = <MultiUseSandbox as AmbiguousIfSync<_>>::assert_not_sync;
1247+
assert_send_sync::<Snapshot>();
1248+
}
1249+
12251250
#[test]
12261251
fn poison() {
12271252
let mut sbox: MultiUseSandbox = {
@@ -2281,6 +2306,8 @@ mod tests {
22812306
.unwrap()
22822307
.evolve()
22832308
.unwrap();
2309+
let source_finder: crate::sandbox::PtRootFinder = Arc::new(|_, _, root| vec![root]);
2310+
source.set_pt_root_finder(source_finder.clone());
22842311
let mut target =
22852312
UninitializedSandbox::new(GuestBinary::FilePath(simple_guest_as_pathbuf()), None)
22862313
.unwrap()
@@ -2289,8 +2316,7 @@ mod tests {
22892316

22902317
assert_eq!(source.call::<i32>("StackAllocate", 256i32).unwrap(), 256);
22912318
assert_eq!(target.call::<i32>("AddToStatic", 17i32).unwrap(), 17);
2292-
target.set_pt_root_finder(Box::new(|_, _, root| vec![root]));
2293-
assert!(target.pt_root_finder.is_some());
2319+
target.set_pt_root_finder(Arc::new(|_, _, _| Vec::new()));
22942320

22952321
assert_ne!(
22962322
source.mem_mgr.layout.code_size(),
@@ -2307,7 +2333,10 @@ mod tests {
23072333

23082334
let snapshot = source.snapshot().unwrap();
23092335
target.restore(snapshot).unwrap();
2310-
assert!(target.pt_root_finder.is_none());
2336+
assert!(Arc::ptr_eq(
2337+
target.pt_root_finder.as_ref().unwrap(),
2338+
&source_finder
2339+
));
23112340
assert_eq!(target.call::<i32>("StackAllocate", 512i32).unwrap(), 512);
23122341
assert!(matches!(
23132342
target.call::<i32>("GetStatic", ()),
@@ -2318,6 +2347,68 @@ mod tests {
23182347
));
23192348
}
23202349

2350+
#[test]
2351+
fn snapshot_restore_clears_absent_pt_root_finder() {
2352+
let path = simple_guest_as_pathbuf();
2353+
let mut source = UninitializedSandbox::new(GuestBinary::FilePath(path), None)
2354+
.unwrap()
2355+
.evolve()
2356+
.unwrap();
2357+
let snapshot = source.snapshot().unwrap();
2358+
assert!(snapshot.pt_root_finder().is_none());
2359+
2360+
let path = simple_guest_as_pathbuf();
2361+
let mut target = UninitializedSandbox::new(GuestBinary::FilePath(path), None)
2362+
.unwrap()
2363+
.evolve()
2364+
.unwrap();
2365+
target.set_pt_root_finder(Arc::new(|_, _, root| vec![root]));
2366+
2367+
target.restore(snapshot).unwrap();
2368+
assert!(target.pt_root_finder.is_none());
2369+
}
2370+
2371+
#[test]
2372+
fn snapshot_restore_uses_retained_pt_root_finder() {
2373+
let source_calls = Arc::new(AtomicUsize::new(0));
2374+
let source_calls_in_finder = source_calls.clone();
2375+
let source_finder: crate::sandbox::PtRootFinder = Arc::new(move |_, _, _| {
2376+
source_calls_in_finder.fetch_add(1, Ordering::Relaxed);
2377+
Vec::new()
2378+
});
2379+
let path = simple_guest_as_pathbuf();
2380+
let mut source = UninitializedSandbox::new(GuestBinary::FilePath(path), None)
2381+
.unwrap()
2382+
.evolve()
2383+
.unwrap();
2384+
source.set_pt_root_finder(source_finder);
2385+
let snapshot = source.snapshot().unwrap();
2386+
2387+
let target_calls = Arc::new(AtomicUsize::new(0));
2388+
let target_calls_in_finder = target_calls.clone();
2389+
let target_finder: crate::sandbox::PtRootFinder = Arc::new(move |_, _, root| {
2390+
target_calls_in_finder.fetch_add(1, Ordering::Relaxed);
2391+
vec![root]
2392+
});
2393+
let path = simple_guest_as_pathbuf();
2394+
let mut target = UninitializedSandbox::new(GuestBinary::FilePath(path), None)
2395+
.unwrap()
2396+
.evolve()
2397+
.unwrap();
2398+
target.set_pt_root_finder(target_finder);
2399+
target.restore(snapshot).unwrap();
2400+
2401+
let source_calls_before = source_calls.load(Ordering::Relaxed);
2402+
target.call::<i32>("GetStatic", ()).unwrap();
2403+
target.snapshot().unwrap();
2404+
2405+
assert_eq!(
2406+
source_calls.load(Ordering::Relaxed),
2407+
source_calls_before + 1
2408+
);
2409+
assert_eq!(target_calls.load(Ordering::Relaxed), 0);
2410+
}
2411+
23212412
#[test]
23222413
fn snapshot_restore_replaces_c_guest_with_rust_guest() {
23232414
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
@@ -27,6 +27,7 @@ use sha2::{Digest as _, Sha256};
2727
use crate::func::Registerable;
2828
use crate::mem::layout::SandboxMemoryLayout;
2929
use crate::mem::shared_mem::SharedMemory as _;
30+
use crate::sandbox::PtRootFinder;
3031
use crate::sandbox::snapshot::{OciDigest, OciReference, OciTag, Snapshot};
3132
use crate::{GuestBinary, HostFunctions, MultiUseSandbox, UninitializedSandbox};
3233

@@ -123,9 +124,21 @@ fn find_snapshot_blob(oci_dir: &std::path::Path) -> std::path::PathBuf {
123124

124125
#[test]
125126
fn from_snapshot_already_initialized_in_memory() {
126-
let snapshot = create_snapshot();
127+
let mut source = create_test_sandbox();
128+
let initial_snapshot = source.snapshot().unwrap();
129+
let finder: PtRootFinder = Arc::new(|_, _, root| vec![root]);
130+
source.set_pt_root_finder(finder.clone());
131+
let snapshot = source.snapshot().unwrap();
132+
assert!(!Arc::ptr_eq(&initial_snapshot, &snapshot));
133+
assert!(Arc::ptr_eq(snapshot.pt_root_finder().unwrap(), &finder));
134+
127135
let mut sbox2 =
128136
MultiUseSandbox::from_snapshot(snapshot, HostFunctions::default(), None).unwrap();
137+
let restored_snapshot = sbox2.snapshot().unwrap();
138+
assert!(Arc::ptr_eq(
139+
restored_snapshot.pt_root_finder().unwrap(),
140+
&finder
141+
));
129142
let result: i32 = sbox2.call("GetStatic", ()).unwrap();
130143
assert_eq!(result, 0);
131144
}
@@ -148,7 +161,9 @@ fn from_snapshot_in_memory_pre_init() {
148161

149162
#[test]
150163
fn round_trip_save_load_call() {
151-
let snapshot = create_snapshot();
164+
let mut source = create_test_sandbox();
165+
source.set_pt_root_finder(Arc::new(|_, _, root| vec![root]));
166+
let snapshot = source.snapshot().unwrap();
152167

153168
let dir = tempfile::tempdir().unwrap();
154169
let oci = dir.path().join("snap");
@@ -157,6 +172,7 @@ fn round_trip_save_load_call() {
157172
.unwrap();
158173

159174
let loaded = Snapshot::checked_load(&oci, OciTag::new("latest").unwrap()).unwrap();
175+
assert!(loaded.pt_root_finder().is_none());
160176
let mut sbox2 =
161177
MultiUseSandbox::from_snapshot(Arc::new(loaded), HostFunctions::default(), None).unwrap();
162178

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());
@@ -593,6 +598,7 @@ impl Snapshot {
593598
original_entrypoint,
594599
snapshot_generation,
595600
host_functions,
601+
pt_root_finder,
596602
})
597603
}
598604

@@ -601,6 +607,10 @@ impl Snapshot {
601607
self.snapshot_generation
602608
}
603609

610+
pub(crate) fn pt_root_finder(&self) -> Option<&PtRootFinder> {
611+
self.pt_root_finder.as_ref()
612+
}
613+
604614
/// Return the main memory contents of the snapshot
605615
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
606616
pub(crate) fn memory(&self) -> &ReadonlySharedMemory {
@@ -792,6 +802,7 @@ mod tests {
792802
0,
793803
1,
794804
HostFunctionDetails::default(),
805+
None,
795806
)
796807
.unwrap();
797808

@@ -812,6 +823,7 @@ mod tests {
812823
0,
813824
2,
814825
HostFunctionDetails::default(),
826+
None,
815827
)
816828
.unwrap();
817829

0 commit comments

Comments
 (0)