Save/load snapshot from disk (oci format)#1465
Conversation
84d17dd to
c4aec72
Compare
c4aec72 to
012bd11
Compare
4cb8124 to
7c8175b
Compare
There was a problem hiding this comment.
Pull request overview
Adds an OCI Image Layout–backed on-disk format for Snapshot, enabling saving a sandbox snapshot to disk and loading it back (with optional digest verification) as a building block for snapshot-driven sandbox creation (per the dependency on #1459).
Changes:
- Implement
Snapshot::to_oci,Snapshot::from_oci, andSnapshot::from_oci_uncheckedusing OCI Image Layout + sha256-addressed blobs. - Add serde-encoded snapshot config schema (arch/hypervisor/ABI gating, entrypoint/sregs, layout, host function signatures) plus extensive validation + tests.
- Extend benchmarks and docs to cover snapshot file save/load paths and usage.
Reviewed changes
Copilot reviewed 12 out of 14 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/hyperlight_host/src/sandbox/snapshot/mod.rs | Wires in the new snapshot OCI file backend modules. |
| src/hyperlight_host/src/sandbox/snapshot/file/mod.rs | Implements OCI layout writer/loader for snapshots (manifest/config/blob handling). |
| src/hyperlight_host/src/sandbox/snapshot/file/config.rs | Defines and validates the JSON config schema for OCI snapshot artifacts. |
| src/hyperlight_host/src/sandbox/snapshot/file/digest.rs | Adds sha256 digest helpers and verification for blobs. |
| src/hyperlight_host/src/sandbox/snapshot/file/fsutil.rs | Adds bounded file read + atomic write + content-addressed blob write helpers. |
| src/hyperlight_host/src/sandbox/snapshot/file/media_types.rs | Defines versioned media types and ABI version constant for snapshot artifacts. |
| src/hyperlight_host/src/sandbox/snapshot/file_tests.rs | Adds comprehensive unit/integration tests for the on-disk OCI snapshot format. |
| src/hyperlight_host/src/sandbox/initialized_multi_use.rs | Updates docs to show creating a sandbox from an on-disk-loaded snapshot. |
| src/hyperlight_host/src/mem/shared_mem.rs | Adjusts Linux file-backed mapping protection flags for MSHV requirements. |
| src/hyperlight_host/Cargo.toml | Adds dependencies needed for OCI + hashing + serde config. |
| src/hyperlight_host/benches/benchmarks.rs | Adds benchmarks for sandbox-from-snapshot and snapshot file save/load/cold-start paths. |
| docs/snapshot-oci-format.md | Documents the OCI Image Layout snapshot on-disk format and semantics. |
| CHANGELOG.md | Notes the new snapshot persistence APIs. |
| Cargo.lock | Locks newly added transitive dependencies. |
| use crate::hypervisor::regs::CommonSpecialRegisters; | ||
| use crate::mem::layout::SandboxMemoryLayout; | ||
| use crate::mem::memory_region::MemoryRegionFlags; | ||
| use crate::mem::shared_mem::{ReadonlySharedMemory, SharedMemory}; |
| // 2. Overlay the file content on the middle slot with | ||
| // `MAP_FIXED`. The guest maps these pages READ|EXECUTE, | ||
| // so the host VMA is read-only. `MAP_PRIVATE` keeps the | ||
| // mapping detached from the underlying file. |
There was a problem hiding this comment.
There's a comment right there
| serde = { version = "1.0", features = ["derive"] } | ||
| serde_json = "1.0" | ||
| elfcore = { version = "2.0", optional = true } | ||
| uuid = { version = "1.23.2", features = ["v4"] } | ||
| oci-spec = { version = "0.8", default-features = false, features = ["image"] } |
7c8175b to
a37f54e
Compare
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
517f50d to
54107fb
Compare
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
54107fb to
f270a7e
Compare
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
f270a7e to
8a542b8
Compare
| bytes: &[u8], | ||
| ) -> crate::Result<()> { | ||
| let target = blobs_dir.join(&digest.hex); | ||
| if let Ok(meta) = std::fs::metadata(&target) |
There was a problem hiding this comment.
Do we want to check for symlinks as well? I think if blob is symlink it should be rejected.
| #[serde(rename_all = "lowercase")] | ||
| pub(super) enum Arch { | ||
| X86_64, | ||
| Aarch64, |
There was a problem hiding this comment.
Does this require synchronization with aarch64 support PR? It looks like registers for arm are placeholders only now.
| // of the snapshot region avoids overlap with | ||
| // `map_file_cow` regions installed immediately after the | ||
| // snapshot in guest PA space. | ||
| let memory = ReadonlySharedMemory::from_file(&snap_file, layout.snapshot_size)?; |
There was a problem hiding this comment.
Should we check memory size here again against cfg.memory_size?
if memory.mem_size() as u64 != cfg.memory_size {
return Err(...);
}Is it possible that file will be modified between verification and mapping or is file immutability enforced somehow? If it can be modified, I think that without this check later restore can explode, no?
let snapshot_pt_end = self.shared_mem.mem_size();
let snapshot_pt_start = snapshot_pt_end - snapshot_pt_siz| } | ||
|
|
||
| impl OciSnapshotConfig { | ||
| pub(super) fn validate_for_load(&self) -> crate::Result<()> { |
There was a problem hiding this comment.
I think we are missing stack_top_gva validation. It looks like it's loaded but not validated.
| } | ||
|
|
||
| // 6. Reconstruct layout. | ||
| let mut sbox_cfg = crate::sandbox::SandboxConfiguration::default(); |
There was a problem hiding this comment.
I think all those config fields that are used to calculate layout arithmetic should be verified/bounded before creating layout?
| pub(super) init_data_permissions: Option<u32>, | ||
| pub(super) scratch_size: usize, | ||
| pub(super) snapshot_size: usize, | ||
| pub(super) pt_size: Option<usize>, |
There was a problem hiding this comment.
Why this is optional? Can we populate 0 size pt?
https://github.com/hyperlight-dev/hyperlight/blob/main/src/hyperlight_host/src/mem/mgr.rs#L624

Adds save/load of
Snapshotto disk as an OCI Image Layout. Public API:Snapshot::to_oci(path, tag)Snapshot::from_oci(path, tag)(verifies sha256 on every blob)Snapshot::from_oci_unchecked(path, tag)(unsafebecause skips digest checks, notunsafein rust UB sense)Known limitations
Core dumps from a snapshot-loaded sandbox lack
binary_pathand AT_ENTRY forCallsnapshots.mem_profilelacks accurate traces. Fixing either requiresextending the on-disk format.
max_guest_log_levelis not plumbed through snapshot load. It is alsointrinsically ineffective for
Callsnapshots and should be addressedseparately.
The backing OCI directory must not be modified, truncated, renamed over, or
deleted for the lifetime of a loaded
Snapshotor anyMultiUseSandboxbuilt from it. On Linux this is unenforced. On Windows the OS refuses the
operation with
ERROR_USER_MAPPED_FILE(1224). Firecracker has the sameconstraint:
firecracker docs
Future work
Typed error variants. TSC and rand seeding capture. Fuzz target for
from_oci.CoW overlay layers. Cross-hypervisor portability via sregs normalisation. Huge
page support (
MAP_HUGETLB)