Skip to content

Commit c66895a

Browse files
syntacticallysimongdavies
authored andcommitted
[hyperlight_host/snapshot] Add deterministic durable identity
Previously, snapshots were compared for equality by checking if they were literally the same object in memory. This replaces that comparison with a deterministic hash of the contents of the sandbox, which can be persisted to and reloaded from disk. Signed-off-by: Simon Davies <[email protected]>
1 parent fda35d2 commit c66895a

File tree

2 files changed

+32
-1
lines changed

2 files changed

+32
-1
lines changed

src/hyperlight_host/src/sandbox/initialized_multi_use.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ impl MultiUseSandbox {
258258
#[instrument(err(Debug), skip_all, parent = Span::current())]
259259
pub fn restore(&mut self, snapshot: Arc<Snapshot>) -> Result<()> {
260260
if let Some(snap) = &self.snapshot
261-
&& Arc::ptr_eq(&snap, &snapshot)
261+
&& snap.as_ref() == snapshot.as_ref()
262262
{
263263
// If the snapshot is already the current one, no need to restore
264264
return Ok(());

src/hyperlight_host/src/sandbox/snapshot.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,29 @@ pub struct Snapshot {
2929
memory: Vec<u8>,
3030
/// The memory regions that were mapped when this snapshot was taken (excluding initial sandbox regions)
3131
regions: Vec<MemoryRegion>,
32+
/// The hash of the other portions of the snapshot. Morally, this
33+
/// is just a memoization cache for [`hash`], below, but it is not
34+
/// a [`std::sync::OnceLock`] because it may be persisted to disk
35+
/// without being recomputed on load.
36+
///
37+
/// It is not a [`blake3::Hash`] because we do not presently
38+
/// require constant-time equality checking
39+
hash: [u8; 32],
40+
}
41+
42+
fn hash(memory: &[u8], regions: &[MemoryRegion]) -> [u8; 32] {
43+
let mut hasher = blake3::Hasher::new();
44+
hasher.update(memory);
45+
for rgn in regions {
46+
hasher.update(&usize::to_le_bytes(rgn.guest_region.start));
47+
let guest_len = rgn.guest_region.end - rgn.guest_region.start;
48+
hasher.update(&usize::to_le_bytes(rgn.guest_region.start));
49+
let host_len = rgn.host_region.end - rgn.host_region.start;
50+
assert!(guest_len == host_len);
51+
hasher.update(&usize::to_le_bytes(guest_len));
52+
hasher.update(&u32::to_le_bytes(rgn.flags.bits()));
53+
}
54+
hasher.finalize().into()
3255
}
3356

3457
impl Snapshot {
@@ -42,10 +65,12 @@ impl Snapshot {
4265
) -> Result<Self> {
4366
// TODO: Track dirty pages instead of copying entire memory
4467
let memory = shared_mem.with_exclusivity(|e| e.copy_all_to_vec())??;
68+
let hash = hash(&memory, &regions);
4569
Ok(Self {
4670
sandbox_id,
4771
memory,
4872
regions,
73+
hash,
4974
})
5075
}
5176

@@ -72,6 +97,12 @@ impl Snapshot {
7297
}
7398
}
7499

100+
impl PartialEq for Snapshot {
101+
fn eq(&self, other: &Snapshot) -> bool {
102+
self.hash == other.hash
103+
}
104+
}
105+
75106
#[cfg(test)]
76107
mod tests {
77108
use hyperlight_common::mem::PAGE_SIZE_USIZE;

0 commit comments

Comments
 (0)