Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions nvx-rs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ changes only when the module's purpose does.
| [`nvx::exec`](#nvxexec) | The execution engine: the streams, credit, and outcome of an execution's two sides, without I/O, for any framing |
| [`nvx::host::runtime`](#nvxhostruntime) | `Runtime`: what every launch shares, the runtime's files and their identity, the hypervisor and its probe, and the launch options |
| [`nvx::host::vm`](#nvxhostvm) | A private VM directory, cold and capture preparation, owned cold-launch command, verified detached launch, confirmed stop, and caller-saved reattachment |
| [`nvx::host::snapshot`](#nvxhostsnapshot) | Strict private snapshot directories, verified metadata, VM compatibility, captured NIC addresses, and time eligibility |
| [`nvx::host::snapshot`](#nvxhostsnapshot) | Strict private snapshots, post-exit capture finishing, verified metadata, VM compatibility, captured NIC addresses, and time eligibility |
| [`nvx::host::config`](#nvxhostconfig) | `VmConfig` and `CaptureConfig`: pure configuration, cold-boot kernel/OpenVMM arguments, portable length checks, and capture identity |
| [`nvx::host::restore`](#nvxhostrestore) | A caller-driven restore to Ready without I/O or a clock, and its peer-verified restore-ready listener |
| [`nvx::host::artifacts`](#nvxhostartifacts) | Locating the OpenVMM executable and the guest artifacts that a VM runs, and their runtime identity |
Expand Down Expand Up @@ -619,8 +619,8 @@ responsive shell input, and a verified clean exit; it downloads no artifact.
### `nvx::host::snapshot`

`SnapshotKey` and `SnapshotManifest` own the compatibility key and strict
OpenVMM manifest reader. `SnapshotManifest::time` exposes the capture's
`TimeContract`, whose `eligibility` checks its restore window at a `HostTime`;
OpenVMM manifest reader. `SnapshotManifest::time` and `SnapshotKey::time` expose
the capture's `TimeContract`, whose `eligibility` checks its restore window at a `HostTime`;
`HostTime::now` samples the host's clock and identities. The existing
`host::template` names remain aliases of these same types.

Expand All @@ -629,6 +629,12 @@ The key's canonical encoding and identity use the snapshot names and omit
and no reader or migration of the prior encoding is supported. `CaptureClass`
still records the legacy capture inputs, including its guest build.

The persisted key includes the complete per-snapshot time contract and must
agree with the manifest's time. Its compatibility identity excludes capture
anchors, host and boot identities, lineage and cut-to-anchor time: captures
at different times can have different key bytes and the same `KeyIdentity`.
The declared rates and time ABI remain compatibility inputs.

`Snapshot::open` checks exactly `key.bin`, `snapshot.json` and
`openvmm/{manifest.bin,memory.bin,state.bin}`. Directories are private, files
are regular and private to their owner, artifact lengths equal the strict
Expand Down Expand Up @@ -676,6 +682,15 @@ effective devices independently of runtime paths or network policy.
`vm::Prepared::capture_context` returns it for the caller to clone and
serialize; NVX owns no capture-context store or persistence format.

`finish_capture(&VmDir, &CaptureContext, &Exited)` requires the capture VM's
proved exit and successful outcome, checks the OpenVMM snapshot against the
context, and atomically writes `key.bin` and informational `snapshot.json`
beside `openvmm/`. It returns `CapturedSnapshot { key }`. The caller moves
only those three entries into its own private, immutable snapshot directory;
logs, outcomes and other VM files remain where they are. A failure can be
corrected and retried while the VM directory and capture context stay intact,
including when one metadata file was already written.

### `nvx::host::config`

`VmConfig`: the configuration of a VM as one value, its processors, its memory
Expand Down
5 changes: 5 additions & 0 deletions nvx-rs/public-api/linux.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3038,6 +3038,8 @@ field nvx::host::snapshot::CaptureClass::runtime
field nvx::host::snapshot::CaptureClass::scratch
method nvx::host::snapshot::CaptureClass::to_bytes
struct nvx::host::snapshot::CaptureContext
struct nvx::host::snapshot::CapturedSnapshot
field nvx::host::snapshot::CapturedSnapshot::key
struct nvx::host::snapshot::ClassIdentity
method nvx::host::snapshot::ClassIdentity::as_bytes
method nvx::host::snapshot::ClassIdentity::from_bytes
Expand Down Expand Up @@ -3115,6 +3117,7 @@ method nvx::host::snapshot::SnapshotKey::processors
method nvx::host::snapshot::SnapshotKey::runtime
method nvx::host::snapshot::SnapshotKey::scratch
method nvx::host::snapshot::SnapshotKey::speaks_current_contracts
method nvx::host::snapshot::SnapshotKey::time
method nvx::host::snapshot::SnapshotKey::to_bytes
struct nvx::host::snapshot::SnapshotManifest
method nvx::host::snapshot::SnapshotManifest::created_at
Expand All @@ -3138,6 +3141,7 @@ field nvx::host::snapshot::TimeContract::host_id
field nvx::host::snapshot::TimeContract::lineage
field nvx::host::snapshot::TimeContract::tsc_frequency_hz
field nvx::host::snapshot::TimeContract::tsc_tolerance_ppm
fn nvx::host::snapshot::finish_capture
mod nvx::host::store
struct nvx::host::store::ArtifactIdentity
method nvx::host::store::ArtifactIdentity::as_str
Expand Down Expand Up @@ -3695,6 +3699,7 @@ method nvx::host::template::TemplateKey::processors
method nvx::host::template::TemplateKey::runtime
method nvx::host::template::TemplateKey::scratch
method nvx::host::template::TemplateKey::speaks_current_contracts
method nvx::host::template::TemplateKey::time
method nvx::host::template::TemplateKey::to_bytes
struct nvx::host::template::TemplateManifest
method nvx::host::template::TemplateManifest::created_at
Expand Down
5 changes: 5 additions & 0 deletions nvx-rs/public-api/windows.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1624,6 +1624,8 @@ field nvx::host::snapshot::CaptureClass::runtime
field nvx::host::snapshot::CaptureClass::scratch
method nvx::host::snapshot::CaptureClass::to_bytes
struct nvx::host::snapshot::CaptureContext
struct nvx::host::snapshot::CapturedSnapshot
field nvx::host::snapshot::CapturedSnapshot::key
struct nvx::host::snapshot::ClassIdentity
method nvx::host::snapshot::ClassIdentity::as_bytes
method nvx::host::snapshot::ClassIdentity::from_bytes
Expand Down Expand Up @@ -1701,6 +1703,7 @@ method nvx::host::snapshot::SnapshotKey::processors
method nvx::host::snapshot::SnapshotKey::runtime
method nvx::host::snapshot::SnapshotKey::scratch
method nvx::host::snapshot::SnapshotKey::speaks_current_contracts
method nvx::host::snapshot::SnapshotKey::time
method nvx::host::snapshot::SnapshotKey::to_bytes
struct nvx::host::snapshot::SnapshotManifest
method nvx::host::snapshot::SnapshotManifest::created_at
Expand All @@ -1724,6 +1727,7 @@ field nvx::host::snapshot::TimeContract::host_id
field nvx::host::snapshot::TimeContract::lineage
field nvx::host::snapshot::TimeContract::tsc_frequency_hz
field nvx::host::snapshot::TimeContract::tsc_tolerance_ppm
fn nvx::host::snapshot::finish_capture
mod nvx::host::store
struct nvx::host::store::ArtifactIdentity
method nvx::host::store::ArtifactIdentity::as_str
Expand Down Expand Up @@ -2280,6 +2284,7 @@ method nvx::host::template::TemplateKey::processors
method nvx::host::template::TemplateKey::runtime
method nvx::host::template::TemplateKey::scratch
method nvx::host::template::TemplateKey::speaks_current_contracts
method nvx::host::template::TemplateKey::time
method nvx::host::template::TemplateKey::to_bytes
struct nvx::host::template::TemplateManifest
method nvx::host::template::TemplateManifest::created_at
Expand Down
1 change: 1 addition & 0 deletions nvx-rs/src/host/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,4 @@ pub mod template;
pub(crate) mod tests;
pub mod transport;
pub mod vm;
mod vm_directory;
49 changes: 24 additions & 25 deletions nvx-rs/src/host/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
//! description. The caller keeps the directory and its entries private and immutable while
//! using the opened snapshot.
//!
//! [`finish_capture`] validates an exited capture and writes its key and informational
//! description without publishing or removing any VM-directory entry.
//!
//! [`SnapshotKey`] derives a compatibility key from a capture's class and manifest.
//! [`SnapshotManifest::read`] reads the manifest that OpenVMM wrote, and
//! [`TimeContract::eligibility`] checks its restore window at a [`HostTime`].
Expand All @@ -12,9 +15,10 @@
//! # Canonical encoding
//!
//! A key and a class each have one canonical encoding, which [`SnapshotKey::to_bytes`] and
//! [`CaptureClass::to_bytes`] write, and whose SHA-256 digest, after a domain of its own, is its
//! identity, a [`KeyIdentity`] or a [`ClassIdentity`]. The encoding starts with its version, and
//! each value in it has exactly one encoding:
//! [`CaptureClass::to_bytes`] write. A key's [`KeyIdentity`] digests its compatibility prefix,
//! excluding the appended capture-time anchors and identities; a [`ClassIdentity`] digests the
//! complete class encoding. Both use SHA-256 after a domain of their own. Each encoding starts
//! with its version, and each value in it has exactly one encoding:
//!
//! - An integer is its fixed number of little-endian bytes: one for a `u8`, two for a `u16`, four
//! for a `u32`, and eight for a `u64`.
Expand All @@ -29,6 +33,7 @@
//! in `tests/vectors/snapshot-key-v1.txt` fix version 1 of a key's encoding and of a class's.

mod canonical;
mod capture;
pub(super) mod eligibility;
pub(super) mod key;
pub(super) mod manifest;
Expand All @@ -54,6 +59,8 @@ use crate::host::{filesystem, platform, sandbox, seal};
use crate::net::Ipv4Cidr;
use crate::{Error, Hypervisor, Result};

pub use self::capture::{CapturedSnapshot, finish_capture};
pub(super) use self::capture::{check_exit, check_snapshot};
pub use self::eligibility::{Downtime, DowntimeSource, Eligibility, HostTime, MAX_DOWNTIME_NS};
pub use self::key::{CaptureClass, ClassIdentity, Invariants, KeyIdentity, SnapshotKey};
pub use self::manifest::{
Expand All @@ -67,6 +74,9 @@ pub const DESCRIPTION_FILE: &str = "snapshot.json";
/// The directory of OpenVMM's manifest, RAM and saved state.
pub const OPENVMM_DIR: &str = "openvmm";

/// OpenVMM's exact platform-snapshot inventory, in name order.
pub(super) const SNAPSHOT_ARTIFACTS: [&str; 3] = [MANIFEST_NAME, "memory.bin", "state.bin"];

/// The checked configuration and effective devices of a blockless platform capture.
///
/// [`crate::host::vm::Prepared::capture_context`] returns it after verifying the actual sparse
Expand Down Expand Up @@ -129,30 +139,10 @@ impl Snapshot {
pub fn open(dir: &Path) -> Result<Self> {
let dir = absolute(&normalized(dir)).map_err(|error| entry_error(dir, error))?;
check_directory(&dir, &[KEY_FILE, DESCRIPTION_FILE, OPENVMM_DIR])?;
let openvmm = dir.join(OPENVMM_DIR);
check_directory(&openvmm, &[MANIFEST_NAME, "memory.bin", "state.bin"])?;
for path in [
dir.join(KEY_FILE),
dir.join(DESCRIPTION_FILE),
openvmm.join(MANIFEST_NAME),
] {
for path in [dir.join(KEY_FILE), dir.join(DESCRIPTION_FILE)] {
check_file(&path)?;
}
let manifest = SnapshotManifest::read(&openvmm)?;
for (name, expected) in [
("memory.bin", manifest.memory_len()),
("state.bin", manifest.state_len()),
] {
let path = openvmm.join(name);
let found = check_file(&path)?.len();
if found != expected {
return Err(Error::io(format!(
"the snapshot artifact {} is {found} bytes long, not the {expected} bytes \
that its manifest records",
path.display()
)));
}
}
let manifest = check_openvmm(&dir.join(OPENVMM_DIR))?;
let path = dir.join(KEY_FILE);
let bytes = regular::open_regular(&path)
.and_then(|file| {
Expand Down Expand Up @@ -372,6 +362,15 @@ fn restore_geometry(path: &Path, description: &str) -> Result<BlockGeometry> {
sandbox::block_geometry(&file, path, description)
}

/// Adds the snapshot format's per-file owner-only checks to the legacy capture checks.
fn check_openvmm(path: &Path) -> Result<SnapshotManifest> {
let manifest = check_snapshot(path)?;
for name in SNAPSHOT_ARTIFACTS {
check_file(&path.join(name))?;
}
Ok(manifest)
}

/// Names the failing snapshot entry while retaining its I/O error.
fn entry_error(path: &Path, error: io::Error) -> Error {
Error::io(format!("cannot examine snapshot entry {}", path.display())).with_source(error)
Expand Down
4 changes: 2 additions & 2 deletions nvx-rs/src/host/snapshot/canonical.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! The canonical encoding of a template's compatibility key and of a capture class: the bytes
//! whose SHA-256 digest is their identity.
//! The canonical encoding of a snapshot key and of a capture class. The key's compatibility
//! prefix, and the complete class encoding, are the bytes whose SHA-256 digest is their identity.
//!
//! Every value has exactly one encoding, and the reader refuses any other:
//!
Expand Down
Loading
Loading