From cfaa69c7ef4aeb2669ccdf86773d15e2b078b169 Mon Sep 17 00:00:00 2001 From: Pedro Henrique Penna Date: Thu, 12 Nov 2026 02:54:57 -0800 Subject: [PATCH 1/2] host: delete the artifact discovery that no NVX program uses nvx::Artifacts, with Artifacts::discover, located the OpenVMM executable, the guest kernel and the Alpine initramfs from the NVX_OPENVMM, NVX_KERNEL and NVX_INITRD variables, NVX_ARTIFACTS_DIR, or an nvx/ release beside the executable, and read their runtime identity from the install metadata of the release or checkout. The old launch, instance and template paths that called it are gone (#1121, #1122), and the CLI has its own discovery: --root, the release or checkout that holds the executable, and NVX_OPENVMM, over ArtifactDir. Once SL6.4 (#1124) makes host::artifacts private to the CLI, Artifacts would have no caller in any feature set, so it goes now, while it is public. It goes whole: the type and its fields, new, artifact_dir, runtime_identity, probe, from_dir, from_release_dir, from_repo_layout, discover, ENV_OPENVMM, ENV_KERNEL, ENV_INITRD, ENV_DIR and BESIDE_EXE_DIR, with the crate root's re-export, the private discover_with and read_json, and check_manifest, which only it called. ArtifactDir::sandbox_artifacts, which returned an Artifacts, goes with ArtifactDir::require_openvmm, which only it called, and so does Guest::from_name, which nothing calls: the CLI reads --guest itself. The public API snapshots lose these 38 lines on each platform. ArtifactDir, its layouts and paths, its runtime identity, Guest and the artifacts' names stay, and so do the CLI's NVX_OPENVMM and NVX_AUTO_DOWNLOAD. contract::manifest_mismatches stays for the CLI's downloads. The crate's example, which the doc test at line 62 compiles, and the README's take the runtime's paths and the SHA-256 digests that identify them from their caller, as examples/external-launch.rs does, rather than discover them. Tests of host::artifacts: - Retired, since they checked only what goes: release_layout_names_the_guest_artifacts, the_directory_variable_accepts_a_repository_checkout, explicit_variables_win_and_must_be_complete, the_directory_variable_precedes_the_executable_directory, discovery_fails_when_no_source_is_present, located_artifacts_have_the_identity_of_their_directory, and incompatible_manifests_are_unavailable, whose mismatches contract's mismatches_name_each_field_with_its_expected_and_declared_values covers. - repository_layout_is_detected_beside_the_guest_sources and this_repository_uses_the_repository_layout check the layouts through ArtifactDir alone. - repository_manifest_declares_the_supported_contract checks the checkout's SOURCE-MANIFEST.json with manifest_mismatches. - guests_name_their_artifacts_as_nvx_py_does and a_release_holds_the_guest_artifacts_in_guest drop their checks of from_name and of the release's discovery. - guest_artifacts_are_required_in_the_order_and_words_of_nvx_py, which checked sandbox_artifacts's order, becomes guest_artifacts_are_required_in_the_words_of_nvx_py, which keeps its checks of require_kernel and require_initramfs. Part of #1122 (SL6.6b). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- nvx-rs/README.md | 35 ++-- nvx-rs/public-api/linux.txt | 38 ---- nvx-rs/public-api/windows.txt | 38 ---- nvx-rs/src/host/artifacts/dir.rs | 53 ++---- nvx-rs/src/host/artifacts/guest.rs | 19 -- nvx-rs/src/host/artifacts/mod.rs | 274 +--------------------------- nvx-rs/src/host/artifacts/tests.rs | 277 +++-------------------------- nvx-rs/src/lib.rs | 33 ++-- 8 files changed, 73 insertions(+), 694 deletions(-) diff --git a/nvx-rs/README.md b/nvx-rs/README.md index 37b40d53f..08a561a1d 100644 --- a/nvx-rs/README.md +++ b/nvx-rs/README.md @@ -923,18 +923,12 @@ host namespace policy or skips the identity assertions. ### `nvx::host::artifacts` -`Artifacts` (also `nvx::Artifacts`): the OpenVMM executable, guest kernel, and -Alpine initramfs, from a release, a repository checkout, the environment, or a -directory next to the executable. Artifacts located in a release or checkout -keep its `ArtifactDir`, and `Artifacts::runtime_identity` reads their -`RuntimeIdentity` from its install metadata; artifacts named by path have none. - `ArtifactDir` and `Guest`: every artifact of the guests that `nvx.py` runs, the OpenVMM executable, the kernel, the Alpine, Ubuntu, and Azure Linux initramfs images and their package manifests, and the Ubuntu distro layer and its manifest, in a checkout's `build/` and `openvmm/target/release/` or a release's -`guest/` and `bin/`, checked in the order and with the messages of `nvx.py`. -`ArtifactPaths` lists them all and serializes as JSON. +`guest/` and `bin/`, checked with the messages of `nvx.py`. `ArtifactPaths` +lists them all and serializes as JSON. `ArtifactDir::runtime_identity` reads the `RuntimeIdentity` of the OpenVMM executable, kernel, and Alpine initramfs from the install metadata, a release's @@ -2542,21 +2536,28 @@ restores one, and finds one again after its caller restarts. use std::path::Path; use std::time::{Duration, Instant}; +use nvx::Hypervisor; use nvx::activation::{MachineIdMode, View}; use nvx::host::config::{Boot, ConsoleAttachment, RootConfig, VmConfig}; -use nvx::host::runtime::{LaunchOptions, Runtime}; +use nvx::host::runtime::{ArtifactIdentity, LaunchOptions, Runtime, RuntimeIdentity}; use nvx::host::vm::{self, VmDir}; -use nvx::{Artifacts, Hypervisor}; fn main() -> Result<(), Box> { - // Locates OpenVMM, the guest kernel, and the Alpine initramfs, with the identity that the - // install metadata of their release or checkout gives them. - let artifacts = Artifacts::discover()?; + // The caller locates OpenVMM, the guest kernel, and the Alpine initramfs, and identifies each + // by the SHA-256 digest that its install metadata records, such as the `SHA256SUMS` of an NVX + // release: here, from the command line. + let arguments: Vec = std::env::args().skip(1).collect(); + let [openvmm, kernel, initramfs, openvmm_sha256, kernel_sha256, initramfs_sha256] = + <[String; 6]>::try_from(arguments).map_err(|_| "expected three paths and three digests")?; let runtime = Runtime { - identity: artifacts.runtime_identity()?, - openvmm: artifacts.openvmm, - kernel: artifacts.kernel, - initramfs: artifacts.initrd, + identity: RuntimeIdentity::new( + ArtifactIdentity::sha256(&openvmm_sha256)?, + ArtifactIdentity::sha256(&kernel_sha256)?, + ArtifactIdentity::sha256(&initramfs_sha256)?, + ), + openvmm: openvmm.into(), + kernel: kernel.into(), + initramfs: initramfs.into(), hypervisor: Hypervisor::from_env_or_default()?, cpu_profile: None, windows_breakaway: false, diff --git a/nvx-rs/public-api/linux.txt b/nvx-rs/public-api/linux.txt index 98eff7faa..9298a3ddf 100644 --- a/nvx-rs/public-api/linux.txt +++ b/nvx-rs/public-api/linux.txt @@ -1,21 +1,4 @@ mod nvx -struct nvx::Artifacts -const nvx::Artifacts::BESIDE_EXE_DIR -const nvx::Artifacts::ENV_DIR -const nvx::Artifacts::ENV_INITRD -const nvx::Artifacts::ENV_KERNEL -const nvx::Artifacts::ENV_OPENVMM -method nvx::Artifacts::artifact_dir -method nvx::Artifacts::discover -method nvx::Artifacts::from_dir -method nvx::Artifacts::from_release_dir -method nvx::Artifacts::from_repo_layout -field nvx::Artifacts::initrd -field nvx::Artifacts::kernel -method nvx::Artifacts::new -field nvx::Artifacts::openvmm -method nvx::Artifacts::probe -method nvx::Artifacts::runtime_identity struct nvx::Error method nvx::Error::code method nvx::Error::exited @@ -1624,33 +1607,14 @@ method nvx::host::artifacts::ArtifactDir::release method nvx::host::artifacts::ArtifactDir::repository method nvx::host::artifacts::ArtifactDir::require_initramfs method nvx::host::artifacts::ArtifactDir::require_kernel -method nvx::host::artifacts::ArtifactDir::require_openvmm method nvx::host::artifacts::ArtifactDir::root method nvx::host::artifacts::ArtifactDir::runtime_identity -method nvx::host::artifacts::ArtifactDir::sandbox_artifacts struct nvx::host::artifacts::ArtifactPaths field nvx::host::artifacts::ArtifactPaths::guests field nvx::host::artifacts::ArtifactPaths::kernel field nvx::host::artifacts::ArtifactPaths::layout field nvx::host::artifacts::ArtifactPaths::openvmm field nvx::host::artifacts::ArtifactPaths::root -struct nvx::host::artifacts::Artifacts -const nvx::host::artifacts::Artifacts::BESIDE_EXE_DIR -const nvx::host::artifacts::Artifacts::ENV_DIR -const nvx::host::artifacts::Artifacts::ENV_INITRD -const nvx::host::artifacts::Artifacts::ENV_KERNEL -const nvx::host::artifacts::Artifacts::ENV_OPENVMM -method nvx::host::artifacts::Artifacts::artifact_dir -method nvx::host::artifacts::Artifacts::discover -method nvx::host::artifacts::Artifacts::from_dir -method nvx::host::artifacts::Artifacts::from_release_dir -method nvx::host::artifacts::Artifacts::from_repo_layout -field nvx::host::artifacts::Artifacts::initrd -field nvx::host::artifacts::Artifacts::kernel -method nvx::host::artifacts::Artifacts::new -field nvx::host::artifacts::Artifacts::openvmm -method nvx::host::artifacts::Artifacts::probe -method nvx::host::artifacts::Artifacts::runtime_identity const nvx::host::artifacts::CHECKSUMS_NAME const nvx::host::artifacts::DISTRO_MANIFEST_SUFFIX enum nvx::host::artifacts::Guest @@ -1662,7 +1626,6 @@ method nvx::host::artifacts::Guest::default_memory_mib method nvx::host::artifacts::Guest::distribution method nvx::host::artifacts::Guest::distro_layer_name method nvx::host::artifacts::Guest::distro_manifest_name -method nvx::host::artifacts::Guest::from_name method nvx::host::artifacts::Guest::initramfs_name method nvx::host::artifacts::Guest::name method nvx::host::artifacts::Guest::package_manifest_name @@ -1683,7 +1646,6 @@ const nvx::host::artifacts::MANIFEST_NAME const nvx::host::artifacts::OPENVMM_PROVENANCE_NAME const nvx::host::artifacts::PACKAGE_MANIFEST_SUFFIX fn nvx::host::artifacts::absolute -fn nvx::host::artifacts::check_manifest fn nvx::host::artifacts::openvmm_executable mod nvx::host::cli (hidden) fn nvx::host::cli::main (hidden) diff --git a/nvx-rs/public-api/windows.txt b/nvx-rs/public-api/windows.txt index 00f6f4132..32b73143b 100644 --- a/nvx-rs/public-api/windows.txt +++ b/nvx-rs/public-api/windows.txt @@ -1,21 +1,4 @@ mod nvx -struct nvx::Artifacts -const nvx::Artifacts::BESIDE_EXE_DIR -const nvx::Artifacts::ENV_DIR -const nvx::Artifacts::ENV_INITRD -const nvx::Artifacts::ENV_KERNEL -const nvx::Artifacts::ENV_OPENVMM -method nvx::Artifacts::artifact_dir -method nvx::Artifacts::discover -method nvx::Artifacts::from_dir -method nvx::Artifacts::from_release_dir -method nvx::Artifacts::from_repo_layout -field nvx::Artifacts::initrd -field nvx::Artifacts::kernel -method nvx::Artifacts::new -field nvx::Artifacts::openvmm -method nvx::Artifacts::probe -method nvx::Artifacts::runtime_identity struct nvx::Error method nvx::Error::code method nvx::Error::exited @@ -352,33 +335,14 @@ method nvx::host::artifacts::ArtifactDir::release method nvx::host::artifacts::ArtifactDir::repository method nvx::host::artifacts::ArtifactDir::require_initramfs method nvx::host::artifacts::ArtifactDir::require_kernel -method nvx::host::artifacts::ArtifactDir::require_openvmm method nvx::host::artifacts::ArtifactDir::root method nvx::host::artifacts::ArtifactDir::runtime_identity -method nvx::host::artifacts::ArtifactDir::sandbox_artifacts struct nvx::host::artifacts::ArtifactPaths field nvx::host::artifacts::ArtifactPaths::guests field nvx::host::artifacts::ArtifactPaths::kernel field nvx::host::artifacts::ArtifactPaths::layout field nvx::host::artifacts::ArtifactPaths::openvmm field nvx::host::artifacts::ArtifactPaths::root -struct nvx::host::artifacts::Artifacts -const nvx::host::artifacts::Artifacts::BESIDE_EXE_DIR -const nvx::host::artifacts::Artifacts::ENV_DIR -const nvx::host::artifacts::Artifacts::ENV_INITRD -const nvx::host::artifacts::Artifacts::ENV_KERNEL -const nvx::host::artifacts::Artifacts::ENV_OPENVMM -method nvx::host::artifacts::Artifacts::artifact_dir -method nvx::host::artifacts::Artifacts::discover -method nvx::host::artifacts::Artifacts::from_dir -method nvx::host::artifacts::Artifacts::from_release_dir -method nvx::host::artifacts::Artifacts::from_repo_layout -field nvx::host::artifacts::Artifacts::initrd -field nvx::host::artifacts::Artifacts::kernel -method nvx::host::artifacts::Artifacts::new -field nvx::host::artifacts::Artifacts::openvmm -method nvx::host::artifacts::Artifacts::probe -method nvx::host::artifacts::Artifacts::runtime_identity const nvx::host::artifacts::CHECKSUMS_NAME const nvx::host::artifacts::DISTRO_MANIFEST_SUFFIX enum nvx::host::artifacts::Guest @@ -390,7 +354,6 @@ method nvx::host::artifacts::Guest::default_memory_mib method nvx::host::artifacts::Guest::distribution method nvx::host::artifacts::Guest::distro_layer_name method nvx::host::artifacts::Guest::distro_manifest_name -method nvx::host::artifacts::Guest::from_name method nvx::host::artifacts::Guest::initramfs_name method nvx::host::artifacts::Guest::name method nvx::host::artifacts::Guest::package_manifest_name @@ -411,7 +374,6 @@ const nvx::host::artifacts::MANIFEST_NAME const nvx::host::artifacts::OPENVMM_PROVENANCE_NAME const nvx::host::artifacts::PACKAGE_MANIFEST_SUFFIX fn nvx::host::artifacts::absolute -fn nvx::host::artifacts::check_manifest fn nvx::host::artifacts::openvmm_executable mod nvx::host::cli (hidden) fn nvx::host::cli::main (hidden) diff --git a/nvx-rs/src/host/artifacts/dir.rs b/nvx-rs/src/host/artifacts/dir.rs index 5654c22ba..33a35cbb7 100644 --- a/nvx-rs/src/host/artifacts/dir.rs +++ b/nvx-rs/src/host/artifacts/dir.rs @@ -9,9 +9,9 @@ use serde::Serialize; use super::identity::{provenance_identity, read_metadata, sha256_identity}; use super::require::required; use super::{ - Artifacts, CHECKSUMS_NAME, Guest, INITRAMFS_PROVENANCE_NAME, KERNEL_NAME, - KERNEL_PROVENANCE_NAME, OPENVMM_PROVENANCE_NAME, RELEASE_BIN_DIR, RELEASE_GUEST_DIR, - REPO_BUILD_DIR, REPO_OPENVMM_DIR, absolute, is_release_dir, openvmm_executable, + CHECKSUMS_NAME, Guest, INITRAMFS_PROVENANCE_NAME, KERNEL_NAME, KERNEL_PROVENANCE_NAME, + OPENVMM_PROVENANCE_NAME, RELEASE_BIN_DIR, RELEASE_GUEST_DIR, REPO_BUILD_DIR, REPO_OPENVMM_DIR, + absolute, is_release_dir, openvmm_executable, }; use crate::error::{Error, Result}; use crate::host::runtime::{ArtifactIdentity, RuntimeIdentity}; @@ -33,8 +33,8 @@ pub enum Layout { /// `scripts/nvx_tools/common.py` give for a checkout. /// /// Paths are joined to the directory, made absolute, without checking that they exist; the -/// `require_*` methods check them with the messages of `nvx.py`. Unlike [`Artifacts`], nothing -/// here reads the release's `SOURCE-MANIFEST.json`, which `nvx.py` does not check either. +/// `require_*` methods check them with the messages of `nvx.py`. Nothing here reads the release's +/// `SOURCE-MANIFEST.json`, which `nvx.py` does not check either. #[derive(Clone, Debug, PartialEq, Eq)] pub struct ArtifactDir { /// The absolute directory. @@ -59,10 +59,12 @@ impl ArtifactDir { Self::new(root.as_ref(), Layout::Release) } - /// Uses a directory in the layout that its built artifacts reveal, as - /// [`Artifacts::from_dir`] tells them apart: a directory that contains `bin/openvmm[.exe]`, - /// `guest/vmlinux`, or `guest/initramfs.cpio.gz` is a release, and any other directory is a - /// repository checkout. + /// Uses a directory in the layout that its built artifacts reveal. + /// + /// A repository checkout also has a `guest/` directory, which holds the guest sources, so the + /// layouts are told apart by their built artifacts instead: a directory that contains + /// `bin/openvmm[.exe]`, `guest/vmlinux`, or `guest/initramfs.cpio.gz` is a release, and any + /// other directory is a repository checkout. /// /// Fails as [`repository`](Self::repository) does. pub fn from_dir(directory: impl AsRef) -> Result { @@ -141,16 +143,6 @@ impl ArtifactDir { .map(|name| self.artifact(&name)) } - /// Checks that the OpenVMM executable is a regular file and returns its path. - /// - /// Fails with [`ErrorCode::ArtifactMissing`] and the message `OpenVMM release binary not - /// found: PATH`. - /// - /// [`ErrorCode::ArtifactMissing`]: crate::ErrorCode::ArtifactMissing - pub fn require_openvmm(&self) -> Result { - required(self.openvmm(), "OpenVMM release binary") - } - /// Checks that the guest kernel is a regular file and returns its path. /// /// Fails with [`ErrorCode::ArtifactMissing`] and the message `Linux direct kernel not found: @@ -175,22 +167,6 @@ impl ArtifactDir { ) } - /// Returns the artifacts of a sandbox, as `nvx-rs sandbox run` and `nvx-rs sandbox start` - /// require them: the OpenVMM executable, the kernel, and the Alpine initramfs, in that order, - /// located in this directory. - /// - /// Fails with [`ErrorCode::ArtifactMissing`] for the first one that is not a regular file, - /// with the messages of [`require_openvmm`](Self::require_openvmm) and - /// [`require_kernel`](Self::require_kernel), or `initramfs not found: PATH`. - /// - /// [`ErrorCode::ArtifactMissing`]: crate::ErrorCode::ArtifactMissing - pub fn sandbox_artifacts(&self) -> Result { - self.require_openvmm()?; - self.require_kernel()?; - required(self.initramfs(Guest::Alpine), "initramfs")?; - Ok(Artifacts::located(self)) - } - /// Returns the paths of every artifact, for a caller to show or record. pub fn paths(&self) -> ArtifactPaths { ArtifactPaths { @@ -212,10 +188,9 @@ impl ArtifactDir { } } - /// Returns the identity of the sandbox runtime of this directory, the one that - /// [`sandbox_artifacts`](Self::sandbox_artifacts) and [`Artifacts::from_dir`] name: its - /// OpenVMM executable, its kernel, and its Alpine initramfs, as the directory's install - /// metadata names them, under the contracts that this crate speaks. + /// Returns the identity of the sandbox runtime of this directory: its OpenVMM executable, its + /// kernel, and its Alpine initramfs, as the directory's install metadata names them, under the + /// contracts that this crate speaks. /// /// The metadata names each artifact by the SHA-256 digest that its build or its package /// recorded, so nothing here reads or hashes an artifact: diff --git a/nvx-rs/src/host/artifacts/guest.rs b/nvx-rs/src/host/artifacts/guest.rs index 1bd29c610..502e13888 100644 --- a/nvx-rs/src/host/artifacts/guest.rs +++ b/nvx-rs/src/host/artifacts/guest.rs @@ -3,7 +3,6 @@ use std::fmt; use super::{DISTRO_MANIFEST_SUFFIX, PACKAGE_MANIFEST_SUFFIX}; -use crate::error::{Error, Result}; /// A Linux guest that `nvx.py` builds and runs, as `scripts/nvx_tools/guests.py` describes it. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -29,24 +28,6 @@ impl Guest { } } - /// Returns the guest named `name`. - /// - /// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) and the message - /// `unsupported guest "NAME"; choose alpine, ubuntu, azurelinux`, which quotes the name with - /// Rust's debug formatting. - pub fn from_name(name: &str) -> Result { - Self::ALL - .into_iter() - .find(|guest| guest.name() == name) - .ok_or_else(|| { - let names: Vec<&str> = Self::ALL.iter().map(|guest| guest.name()).collect(); - Error::unsupported(format!( - "unsupported guest {name:?}; choose {}", - names.join(", ") - )) - }) - } - /// Returns the name of the guest's distribution, as messages show it: `Alpine Linux`, /// `Ubuntu Base`, or `Azure Linux`. pub const fn distribution(self) -> &'static str { diff --git a/nvx-rs/src/host/artifacts/mod.rs b/nvx-rs/src/host/artifacts/mod.rs index ecb757169..5f4cbe30e 100644 --- a/nvx-rs/src/host/artifacts/mod.rs +++ b/nvx-rs/src/host/artifacts/mod.rs @@ -1,9 +1,5 @@ //! Locating the OpenVMM executable and the NVX guest artifacts. //! -//! [`Artifacts`] names the three files that a VM runs. They come from an extracted NVX release -//! archive, an NVX repository checkout, or explicit paths. [`Artifacts::discover`] tries these -//! sources in a fixed order so consumers such as MXC need no NVX-specific configuration. -//! //! [`ArtifactDir`] names every artifact of the guests that `nvx.py` runs, which [`Guest`] //! describes, in a repository checkout or an extracted release: //! @@ -29,17 +25,9 @@ mod guest; mod identity; mod require; -use std::env; -use std::ffi::OsString; -use std::fs; use std::path::{Path, PathBuf}; -use serde_json::Value; - -use crate::contract; -use crate::error::{Error, ErrorCode, Result}; -use crate::host::platform; -use crate::host::runtime::RuntimeIdentity; +use crate::error::{Error, Result}; pub use self::dir::{ArtifactDir, ArtifactPaths, GuestPaths, Layout}; pub use self::guest::Guest; @@ -50,234 +38,6 @@ pub(crate) use self::require::require_existing; /// Name of the release manifest at the root of a release archive or repository checkout. pub const MANIFEST_NAME: &str = "SOURCE-MANIFEST.json"; -/// The OpenVMM executable and the NVX guest kernel and initramfs. -/// -/// Every path is used as given. Resolve relative paths with [`absolute`] before a VM runs them, -/// and check that the files exist, because nothing here does. -/// -/// Artifacts located in a release or a repository checkout keep its [`ArtifactDir`], whose -/// install metadata names their [`runtime_identity`](Self::runtime_identity). Artifacts named by -/// path, through [`new`](Self::new) or the explicit variables of [`discover`](Self::discover), -/// have none: their caller supplies the runtime identity of a launch. -#[derive(Debug, Clone, PartialEq, Eq)] -#[non_exhaustive] -pub struct Artifacts { - /// The `openvmm` executable. - pub openvmm: PathBuf, - /// The NVX guest kernel (`vmlinux`). - pub kernel: PathBuf, - /// The NVX Alpine initramfs (`initramfs.cpio.gz`): the guest userland and its control agent. - pub initrd: PathBuf, - /// The release or checkout in which the artifacts were located, if they were. - directory: Option, -} - -impl Artifacts { - /// Names the three artifacts that a VM needs. - pub fn new( - openvmm: impl Into, - kernel: impl Into, - initrd: impl Into, - ) -> Self { - Self { - openvmm: openvmm.into(), - kernel: kernel.into(), - initrd: initrd.into(), - directory: None, - } - } - - /// Returns the sandbox artifacts of `directory`, which they remember. - fn located(directory: &ArtifactDir) -> Self { - Self { - directory: Some(directory.clone()), - ..Self::new( - directory.openvmm(), - directory.kernel(), - directory.initramfs(Guest::Alpine), - ) - } - } - - /// Returns the release or repository checkout in which the artifacts were located, or `None` - /// for artifacts named by path. - pub fn artifact_dir(&self) -> Option<&ArtifactDir> { - self.directory.as_ref() - } - - /// Returns the runtime identity of these artifacts, which the install metadata of the release - /// or checkout in which they were located names, as [`ArtifactDir::runtime_identity`] reads - /// it, without reading or hashing an artifact. - /// - /// Fails with [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) for artifacts named by - /// path, whose caller supplies their identity instead, for artifacts whose paths no longer - /// name the ones of their directory, and as [`ArtifactDir::runtime_identity`] fails. - pub fn runtime_identity(&self) -> Result { - let Some(directory) = &self.directory else { - return Err(Error::unavailable( - "the artifacts are named by path, so no install metadata identifies them: locate \ - them in an NVX release or repository checkout, or supply their runtime identity", - )); - }; - let located = Self::located(directory); - for (path, expected, description) in [ - (&self.openvmm, &located.openvmm, "OpenVMM executable"), - (&self.kernel, &located.kernel, "guest kernel"), - (&self.initrd, &located.initrd, "guest initramfs"), - ] { - if path != expected { - return Err(Error::unavailable(format!( - "the {description} {} is not the one of {}, whose install metadata \ - identifies the artifacts", - path.display(), - directory.root().display() - ))); - } - } - directory.runtime_identity() - } - - /// Checks that this host can run NVX VMs and that the three files exist. - /// - /// Fails with [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) otherwise, with the - /// code [`ErrorCode::ArtifactMissing`] for a missing file. It does not check the hypervisor; - /// [`probe_hypervisor`](super::runtime::probe_hypervisor) does. - pub fn probe(&self) -> Result<()> { - if !platform::SUPPORTED { - return Err(Error::unavailable( - "the openvmm backend supports Linux and Windows hosts only", - )); - } - for (path, description) in [ - (&self.openvmm, "OpenVMM executable"), - (&self.kernel, "NVX guest kernel"), - (&self.initrd, "NVX guest initramfs"), - ] { - if !path.is_file() { - return Err(Error::new( - ErrorCode::ArtifactMissing, - format!("{description} not found: {}", path.display()), - )); - } - } - Ok(()) - } - - /// Uses an extracted NVX release archive: `bin/openvmm[.exe]`, `guest/vmlinux`, and - /// `guest/initramfs.cpio.gz`. - /// - /// Fails with [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) when the release's - /// `SOURCE-MANIFEST.json` declares an incompatible control contract. - pub fn from_release_dir(release_dir: impl AsRef) -> Result { - Self::from_artifact_dir(&ArtifactDir::release(release_dir.as_ref())?) - } - - /// Uses an NVX repository checkout after `nvx-rs download` or a local build: - /// `openvmm/target/release/openvmm[.exe]`, `build/vmlinux`, and `build/initramfs.cpio.gz`. - pub fn from_repo_layout(repo_root: impl AsRef) -> Result { - Self::from_artifact_dir(&ArtifactDir::repository(repo_root.as_ref())?) - } - - /// Uses a directory in either the release or the repository layout. - /// - /// A repository checkout also has a `guest/` directory, which holds the guest sources, so the - /// layouts are told apart by their built artifacts instead: a directory that contains - /// `bin/openvmm[.exe]`, `guest/vmlinux`, or `guest/initramfs.cpio.gz` is a release, and any - /// other directory is a repository checkout. - pub fn from_dir(directory: impl AsRef) -> Result { - Self::from_artifact_dir(&ArtifactDir::from_dir(directory.as_ref())?) - } - - /// Uses the OpenVMM executable, guest kernel, and Alpine initramfs of `directory`, once its - /// `SOURCE-MANIFEST.json` declares the control contract that this crate speaks. - /// - /// Fails with [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) as - /// [`check_manifest`] does. - fn from_artifact_dir(directory: &ArtifactDir) -> Result { - check_manifest(&directory.root().join(MANIFEST_NAME))?; - Ok(Self::located(directory)) - } - - /// Locates the artifacts on this host. - /// - /// Sources are tried in this order, and the first one present wins: - /// - /// 1. The [`ENV_OPENVMM`](Self::ENV_OPENVMM), [`ENV_KERNEL`](Self::ENV_KERNEL), and - /// [`ENV_INITRD`](Self::ENV_INITRD) variables, which must be set together. - /// 2. The [`ENV_DIR`](Self::ENV_DIR) variable, naming a release or repository directory. - /// 3. The [`BESIDE_EXE_DIR`](Self::BESIDE_EXE_DIR) directory next to the current executable, - /// in the release layout. Consumers ship a release there. - /// - /// Fails with [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) when no source is - /// present or the chosen source is unusable. It does not check that the files exist. - pub fn discover() -> Result { - let exe_dir = env::current_exe() - .ok() - .and_then(|exe| exe.parent().map(Path::to_path_buf)); - discover_with(|name| env::var_os(name), exe_dir.as_deref()) - } - - /// Environment variable naming the `openvmm` executable. - pub const ENV_OPENVMM: &'static str = "NVX_OPENVMM"; - /// Environment variable naming the guest kernel. - pub const ENV_KERNEL: &'static str = "NVX_KERNEL"; - /// Environment variable naming the control initramfs. - pub const ENV_INITRD: &'static str = "NVX_INITRD"; - /// Environment variable naming a release or repository directory. - pub const ENV_DIR: &'static str = "NVX_ARTIFACTS_DIR"; - /// Name of the release-layout directory that [`Artifacts::discover`] looks for next to the - /// current executable. - pub const BESIDE_EXE_DIR: &'static str = "nvx"; -} - -/// Implements artifact discovery with an injectable environment and executable directory for -/// tests. -fn discover_with( - variable: impl Fn(&str) -> Option, - exe_dir: Option<&Path>, -) -> Result { - let explicit = [ - Artifacts::ENV_OPENVMM, - Artifacts::ENV_KERNEL, - Artifacts::ENV_INITRD, - ] - .map(|name| variable(name).filter(|value| !value.is_empty())); - match explicit { - [Some(openvmm), Some(kernel), Some(initrd)] => { - return Ok(Artifacts::new(openvmm, kernel, initrd)); - } - [None, None, None] => {} - _ => { - return Err(Error::unavailable(format!( - "set all or none of {}, {}, and {}", - Artifacts::ENV_OPENVMM, - Artifacts::ENV_KERNEL, - Artifacts::ENV_INITRD - ))); - } - } - if let Some(directory) = variable(Artifacts::ENV_DIR).filter(|value| !value.is_empty()) { - return Artifacts::from_dir(PathBuf::from(directory)); - } - if let Some(directory) = exe_dir.map(|directory| directory.join(Artifacts::BESIDE_EXE_DIR)) - && directory.join(MANIFEST_NAME).is_file() - { - return Artifacts::from_release_dir(directory); - } - Err(Error::new( - ErrorCode::ArtifactMissing, - format!( - "cannot locate the OpenVMM executable and NVX guest artifacts: set {} or {}, {}, and \ - {}, or place an NVX release in a {:?} directory next to the executable", - Artifacts::ENV_DIR, - Artifacts::ENV_OPENVMM, - Artifacts::ENV_KERNEL, - Artifacts::ENV_INITRD, - Artifacts::BESIDE_EXE_DIR, - ), - )) -} - /// Returns whether `directory` is in the release layout: whether it contains `bin/openvmm[.exe]`, /// `guest/vmlinux`, or `guest/initramfs.cpio.gz`. fn is_release_dir(directory: &Path) -> bool { @@ -308,38 +68,6 @@ pub fn absolute(path: &Path) -> Result { }) } -/// Reads and parses the JSON file at `path`, naming it with `description` in errors. -fn read_json(path: &Path, description: &str) -> Result { - let text = fs::read_to_string(path).map_err(|error| { - Error::unavailable(format!("cannot read {description} {}", path.display())) - .with_source(error) - })?; - serde_json::from_str(&text).map_err(|error| { - Error::unavailable(format!( - "{description} {} is not valid JSON", - path.display() - )) - .with_source(error) - }) -} - -/// Checks that an NVX `SOURCE-MANIFEST.json` declares the control contract this crate speaks, -/// failing with [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) that names each -/// mismatching contract field. -pub fn check_manifest(path: &Path) -> Result<()> { - let manifest = read_json(path, "NVX manifest")?; - let mismatches = contract::manifest_mismatches(&manifest); - if mismatches.is_empty() { - Ok(()) - } else { - Err(Error::unavailable(format!( - "NVX manifest {} does not declare the compatible OpenVMM contracts ({})", - path.display(), - mismatches.join("; ") - ))) - } -} - /// File name of the guest kernel in both layouts. pub const KERNEL_NAME: &str = "vmlinux"; /// Suffix of the package manifest that lists the packages of an initramfs. diff --git a/nvx-rs/src/host/artifacts/tests.rs b/nvx-rs/src/host/artifacts/tests.rs index 33c1489e5..a0a83005f 100644 --- a/nvx-rs/src/host/artifacts/tests.rs +++ b/nvx-rs/src/host/artifacts/tests.rs @@ -1,11 +1,14 @@ -//! Tests of the discovery of artifacts, of the directories that hold them, and of their +//! Tests of the directories that hold the artifacts, of the guests' artifacts, and of their //! identities. -use std::collections::HashMap; +use std::fs; + +use serde_json::Value; use super::*; use crate::ErrorKind; -use crate::host::runtime::ArtifactIdentity; +use crate::contract; +use crate::host::runtime::{ArtifactIdentity, RuntimeIdentity}; const COMPATIBLE_MANIFEST: &str = r#"{"openvmm":{"microvm_abi_version":1, "time_abi_version":1,"restore_packet_version":1, @@ -33,203 +36,47 @@ fn checkout(root: &Path) { fs::write(root.join(MANIFEST_NAME), COMPATIBLE_MANIFEST).unwrap(); } -fn variables(pairs: &[(&str, &Path)]) -> impl Fn(&str) -> Option { - let map: HashMap = pairs - .iter() - .map(|(name, value)| ((*name).to_owned(), value.as_os_str().to_owned())) - .collect(); - move |name| map.get(name).cloned() -} - -#[test] -fn release_layout_names_the_guest_artifacts() { - let directory = tempfile::tempdir().unwrap(); - release(directory.path()); - let artifacts = Artifacts::from_dir(directory.path()).unwrap(); - let root = std::path::absolute(directory.path()).unwrap(); - assert_eq!( - artifacts.openvmm, - root.join("bin").join(openvmm_executable()) - ); - assert_eq!(artifacts.kernel, root.join("guest").join("vmlinux")); - assert_eq!( - artifacts.initrd, - root.join("guest").join("initramfs.cpio.gz") - ); - // A directory that the caller hands over, such as a `TempDir`, lives until the - // constructor returns, after it reads the manifest. - assert_eq!(Artifacts::from_release_dir(directory).unwrap(), artifacts); -} - #[test] fn repository_layout_is_detected_beside_the_guest_sources() { let directory = tempfile::tempdir().unwrap(); checkout(directory.path()); let root = std::path::absolute(directory.path()).unwrap(); - let expected = |artifacts: &Artifacts| { - assert_eq!(artifacts.kernel, root.join("build").join("vmlinux")); + let expected = |artifacts: &ArtifactDir| { + assert_eq!(artifacts.layout(), Layout::Repository); + assert_eq!(artifacts.kernel(), root.join("build").join("vmlinux")); assert_eq!( - artifacts.initrd, + artifacts.initramfs(Guest::Alpine), root.join("build").join("initramfs.cpio.gz") ); assert_eq!( - artifacts.openvmm, + artifacts.openvmm(), root.join("openvmm") .join("target") .join("release") .join(openvmm_executable()) ); }; - expected(&Artifacts::from_dir(directory.path()).unwrap()); - expected(&Artifacts::from_repo_layout(directory.path()).unwrap()); + expected(&ArtifactDir::from_dir(directory.path()).unwrap()); // Build outputs do not turn a checkout into a release. let build = directory.path().join("build"); fs::create_dir_all(&build).unwrap(); fs::write(build.join("vmlinux"), b"kernel").unwrap(); fs::write(build.join("initramfs.cpio.gz"), b"initrd").unwrap(); - expected(&Artifacts::from_dir(directory.path()).unwrap()); -} - -#[test] -fn the_directory_variable_accepts_a_repository_checkout() { - let directory = tempfile::tempdir().unwrap(); - checkout(directory.path()); - let artifacts = - discover_with(variables(&[(Artifacts::ENV_DIR, directory.path())]), None).unwrap(); - let build = std::path::absolute(directory.path()).unwrap().join("build"); - assert_eq!(artifacts.kernel, build.join("vmlinux")); + expected(&ArtifactDir::from_dir(directory.path()).unwrap()); } #[test] fn this_repository_uses_the_repository_layout() { let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap(); if root.join(MANIFEST_NAME).is_file() { - let artifacts = Artifacts::from_dir(root).unwrap(); - let kernel = std::path::absolute(root).unwrap().join("build"); - assert_eq!(artifacts.kernel, kernel.join("vmlinux")); let directory = ArtifactDir::from_dir(root).unwrap(); assert_eq!(directory.layout(), Layout::Repository); - assert_eq!(directory.kernel(), artifacts.kernel); + let build = std::path::absolute(root).unwrap().join("build"); + assert_eq!(directory.kernel(), build.join("vmlinux")); } } -#[test] -fn explicit_variables_win_and_must_be_complete() { - let directory = tempfile::tempdir().unwrap(); - release(directory.path()); - let (openvmm, kernel, initrd) = (Path::new("o"), Path::new("k"), Path::new("i")); - let artifacts = discover_with( - variables(&[ - (Artifacts::ENV_OPENVMM, openvmm), - (Artifacts::ENV_KERNEL, kernel), - (Artifacts::ENV_INITRD, initrd), - (Artifacts::ENV_DIR, directory.path()), - ]), - None, - ) - .unwrap(); - assert_eq!(artifacts, Artifacts::new(openvmm, kernel, initrd)); - - let error = discover_with(variables(&[(Artifacts::ENV_OPENVMM, openvmm)]), None).unwrap_err(); - assert_eq!(error.kind(), ErrorKind::Unavailable); -} - -#[test] -fn the_directory_variable_precedes_the_executable_directory() { - let variable_dir = tempfile::tempdir().unwrap(); - release(variable_dir.path()); - let exe_dir = tempfile::tempdir().unwrap(); - release(&exe_dir.path().join(Artifacts::BESIDE_EXE_DIR)); - - let artifacts = discover_with( - variables(&[(Artifacts::ENV_DIR, variable_dir.path())]), - Some(exe_dir.path()), - ) - .unwrap(); - assert!( - artifacts - .kernel - .starts_with(std::path::absolute(variable_dir.path()).unwrap()) - ); - - let artifacts = discover_with(variables(&[]), Some(exe_dir.path())).unwrap(); - assert!( - artifacts.kernel.starts_with( - std::path::absolute(exe_dir.path().join(Artifacts::BESIDE_EXE_DIR)).unwrap() - ) - ); -} - -#[test] -fn discovery_fails_when_no_source_is_present() { - let exe_dir = tempfile::tempdir().unwrap(); - let error = discover_with(variables(&[]), Some(exe_dir.path())).unwrap_err(); - assert_eq!(error.kind(), ErrorKind::Unavailable); - assert!(error.message().contains(Artifacts::ENV_DIR)); - // No build-time bundle remains to suggest. - assert!(!error.message().contains("bundled")); -} - -#[test] -fn incompatible_manifests_are_unavailable() { - let directory = tempfile::tempdir().unwrap(); - let manifest = directory.path().join(MANIFEST_NAME); - fs::write( - &manifest, - r#"{"openvmm":{"microvm_abi_version":1,"time_abi_version":1, - "restore_packet_version":1,"control_session_protocol_version":2, - "control_contract_revision":"nvx-microvm-v2-control-v2"}}"#, - ) - .unwrap(); - let error = check_manifest(&manifest).unwrap_err(); - assert_eq!(error.kind(), ErrorKind::Unavailable); - assert!( - error - .message() - .contains("(control_session_protocol_version: expected 1, found 2)"), - "{}", - error.message() - ); - // A release of an earlier OpenVMM declares the current control contracts, so the error names - // the microVM ABI, time ABI, and restore packet versions that its microVM had. - fs::write( - &manifest, - r#"{"openvmm":{"microvm_abi_version":3,"time_abi_version":2, - "restore_packet_version":5,"control_session_protocol_version":1, - "control_contract_revision":"nvx-microvm-v2-control-v2"}}"#, - ) - .unwrap(); - let error = check_manifest(&manifest).unwrap_err(); - assert!( - error.message().contains( - "(microvm_abi_version: expected 1, found 3; time_abi_version: expected 1, found 2; \ - restore_packet_version: expected 1, found 5)" - ), - "{}", - error.message() - ); - // A release from before time ABI 2 declares the current control revision, so the error names - // the microVM ABI, time ABI, and restore packet versions that differ. - fs::write( - &manifest, - r#"{"openvmm":{"microvm_abi_version":2,"control_session_protocol_version":1, - "control_contract_revision":"nvx-microvm-v2-control-v2"}}"#, - ) - .unwrap(); - let error = check_manifest(&manifest).unwrap_err(); - assert!( - error.message().contains( - "(microvm_abi_version: expected 1, found 2; time_abi_version: expected 1, found \ - none; restore_packet_version: expected 1, found none)" - ), - "{}", - error.message() - ); - let error = check_manifest(&directory.path().join("missing.json")).unwrap_err(); - assert_eq!(error.kind(), ErrorKind::Unavailable); -} - #[test] fn repository_manifest_declares_the_supported_contract() { let manifest = Path::new(env!("CARGO_MANIFEST_DIR")) @@ -237,7 +84,10 @@ fn repository_manifest_declares_the_supported_contract() { .unwrap() .join(MANIFEST_NAME); if manifest.is_file() { - check_manifest(&manifest).unwrap(); + let manifest: Value = + serde_json::from_str(&fs::read_to_string(&manifest).unwrap()).unwrap(); + let mismatches = contract::manifest_mismatches(&manifest); + assert!(mismatches.is_empty(), "{mismatches:?}"); } } @@ -248,15 +98,8 @@ fn guests_name_their_artifacts_as_nvx_py_does() { ["alpine", "ubuntu", "azurelinux"] ); for guest in Guest::ALL { - assert_eq!(Guest::from_name(guest.name()).unwrap(), guest); assert_eq!(guest.to_string(), guest.name()); } - let error = Guest::from_name("Ubuntu").unwrap_err(); - assert_eq!(error.kind(), ErrorKind::Unsupported); - assert_eq!( - error.message(), - "unsupported guest \"Ubuntu\"; choose alpine, ubuntu, azurelinux" - ); assert_eq!( Guest::ALL.map(Guest::distribution), ["Alpine Linux", "Ubuntu Base", "Azure Linux"] @@ -359,14 +202,10 @@ fn a_release_holds_the_guest_artifacts_in_guest() { artifacts.distro_layer(Guest::Ubuntu), Some(guest.join("ubuntu-distro.erofs")) ); - - // The release's artifacts are the ones that discovery finds for its directory. - let discovered = Artifacts::from_dir(directory.path()).unwrap(); - assert_eq!(artifacts.sandbox_artifacts().unwrap(), discovered); } #[test] -fn guest_artifacts_are_required_in_the_order_and_words_of_nvx_py() { +fn guest_artifacts_are_required_in_the_words_of_nvx_py() { let directory = tempfile::tempdir().unwrap(); checkout(directory.path()); let artifacts = ArtifactDir::repository(directory.path()).unwrap(); @@ -383,39 +222,11 @@ fn guest_artifacts_are_required_in_the_order_and_words_of_nvx_py() { fs::write(path, b"artifact").unwrap(); }; missing( - artifacts.sandbox_artifacts(), - "OpenVMM release binary", - artifacts.openvmm(), - ); - create(artifacts.openvmm()); - missing( - artifacts.sandbox_artifacts(), + artifacts.require_kernel(), "Linux direct kernel", artifacts.kernel(), ); create(artifacts.kernel()); - missing( - artifacts.sandbox_artifacts(), - "initramfs", - artifacts.initramfs(Guest::Alpine), - ); - create(artifacts.initramfs(Guest::Alpine)); - let located = artifacts.sandbox_artifacts().unwrap(); - assert_eq!( - [&located.openvmm, &located.kernel, &located.initrd], - [ - &artifacts.openvmm(), - &artifacts.kernel(), - &artifacts.initramfs(Guest::Alpine) - ] - ); - assert_eq!(located.artifact_dir(), Some(&artifacts)); - assert_eq!( - located, - Artifacts::from_repo_layout(directory.path()).unwrap() - ); - - assert_eq!(artifacts.require_openvmm().unwrap(), artifacts.openvmm()); assert_eq!(artifacts.require_kernel().unwrap(), artifacts.kernel()); missing( artifacts.require_initramfs(Guest::Ubuntu), @@ -683,49 +494,3 @@ fn metadata_that_is_not_a_regular_file_fails_at_once() { drop(writer); } } - -#[test] -fn located_artifacts_have_the_identity_of_their_directory() { - let directory = tempfile::tempdir().unwrap(); - release(directory.path()); - let digests = ["4", "5", "6"].map(|digit| digit.repeat(64)); - let text: String = [ - (&digests[0], format!("bin/{}", openvmm_executable())), - (&digests[1], "guest/vmlinux".to_owned()), - (&digests[2], "guest/initramfs.cpio.gz".to_owned()), - ] - .iter() - .map(|(digest, name)| format!("{digest} {name}\n")) - .collect(); - fs::write(directory.path().join(CHECKSUMS_NAME), text).unwrap(); - let located = Artifacts::from_dir(directory.path()).unwrap(); - assert_eq!( - located.artifact_dir(), - Some(&ArtifactDir::release(directory.path()).unwrap()) - ); - assert_eq!(located.runtime_identity().unwrap(), identity_of(&digests)); - - // Artifacts named by path have no install metadata. - let named = Artifacts::new(&located.openvmm, &located.kernel, &located.initrd); - assert_eq!(named.artifact_dir(), None); - let error = named.runtime_identity().unwrap_err(); - assert_eq!(error.kind(), ErrorKind::Unavailable); - assert!( - error.message().contains("supply their runtime identity"), - "{error}" - ); - // Nor does an artifact that replaced one of the directory's. - let mut replaced = located.clone(); - replaced.kernel = directory.path().join("vmlinux"); - let error = replaced.runtime_identity().unwrap_err(); - assert_eq!(error.kind(), ErrorKind::Unavailable); - assert_eq!( - error.message(), - format!( - "the guest kernel {} is not the one of {}, whose install metadata identifies the \ - artifacts", - replaced.kernel.display(), - located.artifact_dir().unwrap().root().display() - ) - ); -} diff --git a/nvx-rs/src/lib.rs b/nvx-rs/src/lib.rs index dd1366e10..80e3f671b 100644 --- a/nvx-rs/src/lib.rs +++ b/nvx-rs/src/lib.rs @@ -27,9 +27,9 @@ //! NVX writes and reads, and in the layout in which GPT stores them. It has no dependencies, so //! the host and the guest share it. //! - [`Hypervisor`]: the hypervisors that run the VMs. -//! - [`host`] (feature `host`): artifact discovery, the OpenVMM command line, the lowering of -//! network and filesystem policies, the host's NVXS connection, process and platform -//! primitives, VMs that a caller creates, starts, reattaches to, and stops in private +//! - [`host`] (feature `host`): the layouts of NVX's artifacts, the OpenVMM command line, the +//! lowering of network and filesystem policies, the host's NVXS connection, process and +//! platform primitives, VMs that a caller creates, starts, reattaches to, and stops in private //! directories that it owns, and the manifests and compatibility keys of templates. //! - `guest` (feature `guest`, Linux): the programs that run in NVX guests, such as //! `nvx-console-pending`, the guest's NVXS connection, the libraries through which any guest @@ -60,20 +60,27 @@ //! use std::path::Path; //! use std::time::{Duration, Instant}; //! +//! use nvx::Hypervisor; //! use nvx::activation::{MachineIdMode, View}; //! use nvx::host::config::{Boot, ConsoleAttachment, RootConfig, VmConfig}; -//! use nvx::host::runtime::{LaunchOptions, Runtime}; +//! use nvx::host::runtime::{ArtifactIdentity, LaunchOptions, Runtime, RuntimeIdentity}; //! use nvx::host::vm::{self, VmDir}; -//! use nvx::{Artifacts, Hypervisor}; //! -//! // Locates OpenVMM, the guest kernel, and the Alpine initramfs, with the identity that the -//! // install metadata of their release or checkout gives them. -//! let artifacts = Artifacts::discover()?; +//! // The caller locates OpenVMM, the guest kernel, and the Alpine initramfs, and identifies each +//! // by the SHA-256 digest that its install metadata records, such as the `SHA256SUMS` of an NVX +//! // release: here, from the command line. +//! let arguments: Vec = std::env::args().skip(1).collect(); +//! let [openvmm, kernel, initramfs, openvmm_sha256, kernel_sha256, initramfs_sha256] = +//! <[String; 6]>::try_from(arguments).map_err(|_| "expected three paths and three digests")?; //! let runtime = Runtime { -//! identity: artifacts.runtime_identity()?, -//! openvmm: artifacts.openvmm, -//! kernel: artifacts.kernel, -//! initramfs: artifacts.initrd, +//! identity: RuntimeIdentity::new( +//! ArtifactIdentity::sha256(&openvmm_sha256)?, +//! ArtifactIdentity::sha256(&kernel_sha256)?, +//! ArtifactIdentity::sha256(&initramfs_sha256)?, +//! ), +//! openvmm: openvmm.into(), +//! kernel: kernel.into(), +//! initramfs: initramfs.into(), //! hypervisor: Hypervisor::from_env_or_default()?, //! cpu_profile: None, //! windows_breakaway: false, @@ -154,7 +161,5 @@ mod tests; pub use error::{Error, ErrorCode, ErrorKind, Result}; #[cfg(feature = "host")] -pub use host::artifacts::Artifacts; -#[cfg(feature = "host")] pub use host::filesystem::{guest_path, resolve_guest_path}; pub use hypervisor::Hypervisor; From 7e7ef6e2f4a739f22b7e25e16c1de1596bb52dc1 Mon Sep 17 00:00:00 2001 From: Pedro Henrique Penna Date: Thu, 12 Nov 2026 02:56:05 -0800 Subject: [PATCH 2/2] host: delete the binding and checked-file accessors that nothing reads Since the checked-file handoff left (SL6.3, #1122), a start checks the bindings of its sandbox's images and holds what the checks return, but launches OpenVMM by path: it drops the checked distro layer once the VM runs, and hands the VMM the scratch image's claim, through CheckedScratch::file on Linux and CheckedScratch::claim and into_file on Windows. What the old launch and the template store read of a binding or a checked file, and the import of a layer that a staging caller verified itself, no NVX program reads any more. Once SL6.4 (#1124) makes host::sandbox's distro and scratch modules and host::seal private to the CLI, they would have no caller in any feature set, so they go now, while they are public. What goes: - DistroBinding::import and its private import_with, and the getters format, identity, image, manifest and policy; - CheckedDistro's file, path, geometry and into_file, with its path and format, which only they read; it keeps the checked layer, which a start holds open, as a field that nothing reads; - CheckedScratch's path and geometry, with its path, file on Windows and into_file on Linux; - ScratchBinding's scheme, device, file_id, kind and length; - FileSeal::modified and FileSeal::changed. The public API snapshots lose these 20 lines on each platform. What the bindings record and check, their serde forms, the claims of scratch images, and the block geometry that the private block_geometry reads for host::vm, host::snapshot and the CLI's captures stay. The docs of block_geometry take the description of the geometry that CheckedDistro::geometry gave, and the README and host::sandbox's docs no longer present the import or the checked files' geometry. Tests: imports_bind_a_staged_layer_without_reading_it retires with the import. The other tests of distro bindings, scratch bindings and seals read the private fields of the values that they check in place of the deleted getters, and make the bindings that they need by verification, which reads nothing of a layer without a manifest, or by changing a verified binding's verdict, in place of an import: - distro: verification_binds_the_layer_its_manifest_and_the_policy, bindings_round_trip_through_their_serde_form, the_staged_check_reads_no_content, the_staged_check_fails_without_reading_for_another_policy_or_layer, the_checked_file_is_the_object_that_the_check_compared, gpt_disks_bind_by_their_disk_guid, which no longer compares the verification of a disk with its import, and a_manifest_path_whose_lookup_fails_is_no_absent_manifest_for_a_binding, which no longer checks an import of such a path; - distro: checked_layers_present_the_geometry_that_openvmm_reads_through_them and layers_present_the_block_size_of_their_filesystem, and scratch: checked_images_present_the_geometry_that_openvmm_reads_through_them, read the geometry through the checked file with block_geometry; - scratch: binds_record_the_image_without_reading_it, bindings_round_trip_through_their_serde_form, checks_claim_the_image_without_reading_it, which no longer checks the deleted path, a_check_that_waited_for_a_claim_compares_the_image_again and the_checked_image_is_the_object_that_the_check_compared; - seal: a_seal_holds_the_identity_kind_length_and_times_of_an_open_file and seals_differ_when_their_file_changes; - options: distro_manifest_binds_the_layer, the_entrypoint_check_needs_no_manifest_and_precedes_the_distro_verification and sandbox_rejects_systemd_from_bound_distro_metadata read the binding's serde form, which callers record; - the security checklist's a_staged_image_that_was_replaced_fails_its_check binds its layer, which has no manifest, by verification. Part of #1122 (SL6.6b). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- nvx-rs/README.md | 31 ++--- nvx-rs/public-api/linux.txt | 20 ---- nvx-rs/public-api/windows.txt | 20 ---- nvx-rs/src/host/sandbox/distro.rs | 133 ++------------------- nvx-rs/src/host/sandbox/distro/tests.rs | 140 ++++++++--------------- nvx-rs/src/host/sandbox/mod.rs | 14 ++- nvx-rs/src/host/sandbox/options/tests.rs | 14 ++- nvx-rs/src/host/sandbox/scratch.rs | 63 +--------- nvx-rs/src/host/sandbox/scratch/tests.rs | 37 +++--- nvx-rs/src/host/seal.rs | 10 -- nvx-rs/src/host/seal/tests.rs | 8 +- nvx-rs/tests/security_checklist.rs | 4 +- 12 files changed, 120 insertions(+), 374 deletions(-) diff --git a/nvx-rs/README.md b/nvx-rs/README.md index 08a561a1d..abc8e4856 100644 --- a/nvx-rs/README.md +++ b/nvx-rs/README.md @@ -1101,20 +1101,13 @@ distro layer, the targets of `nvx-rs run` shares, and the managed exec environment; see [Sandbox input contract](#sandbox-input-contract). `distro`: the `DistroBinding` of a distro layer, an EROFS layer or a GPT disk, -which explicit verification makes or a staging caller imports, and its check -from metadata alone, which returns the layer open; see -[Distro bindings](#distro-bindings). +which explicit verification makes, and its check from metadata alone, which +returns the layer open; see [Distro bindings](#distro-bindings). `scratch`: the `ScratchBinding` of a scratch image, its identity, kind, and length, and its check from metadata alone, which claims the image for one sandbox at a time and returns it open. -`CheckedDistro::geometry` and `CheckedScratch::geometry` read, through the -checked files, the `BlockGeometry` that OpenVMM presents for them: their length, -a nonzero multiple of 512 bytes, logical blocks of 512 bytes, and physical -blocks of the I/O block size of their filesystem on Linux, and of 4096 bytes on -Windows. - ### `nvx::guest` The programs that run in Linux guests; see [Guest binaries](#guest-binaries). @@ -1689,17 +1682,15 @@ manifest, or to the manifest's absence, to the revision and result of the distro policy, `DISTRO_POLICY_REVISION`, and to the layer's format and the identity that the guest expects of it: the UUID of an EROFS layer, or the disk GUID of a GPT disk, which `SandboxLayer::gpt` names. It comes from explicit -verification, or from `DistroBinding::import`, which a staging caller that -verified the layer itself calls to bind it without hashing it again. `check` -fails for a binding of another version, layer, format, or policy revision, a verdict -that rejects the layer, a layer whose identity, kind, length, or times changed, -and a manifest that appeared, disappeared, or changed. A binding of a layer -without a manifest holds only while nothing is at the manifest's path: unlike -verification, which finds a manifest as `Path.exists` does, `check` and -`import` refuse a path whose lookup fails for another reason than a missing -file. A missing or stale binding fails: no runtime operation registers, -verifies, or hashes a layer on its own. Hashing belongs where layers are built, -installed, or staged, and to diagnostics. +verification. `check` fails for a binding of another version, layer, format, +or policy revision, a verdict that rejects the layer, a layer whose identity, +kind, length, or times changed, and a manifest that appeared, disappeared, or +changed. A binding of a layer without a manifest holds only while nothing is at +the manifest's path: unlike verification, which finds a manifest as +`Path.exists` does, `check` refuses a path whose lookup fails for another +reason than a missing file. A missing or stale binding fails: no runtime +operation registers, verifies, or hashes a layer on its own. Hashing belongs +where layers are built, installed, or staged, and to diagnostics. On Linux, a seal holds `st_dev`, `st_ino`, the file type, `st_size`, and `st_mtime` and `st_ctime` with their nanoseconds; on Windows, the volume serial diff --git a/nvx-rs/public-api/linux.txt b/nvx-rs/public-api/linux.txt index 9298a3ddf..bf87e14ef 100644 --- a/nvx-rs/public-api/linux.txt +++ b/nvx-rs/public-api/linux.txt @@ -2125,19 +2125,9 @@ method nvx::host::sandbox::SandboxMount::with_kind mod nvx::host::sandbox::distro const nvx::host::sandbox::distro::BINDING_VERSION struct nvx::host::sandbox::distro::CheckedDistro -method nvx::host::sandbox::distro::CheckedDistro::file -method nvx::host::sandbox::distro::CheckedDistro::geometry -method nvx::host::sandbox::distro::CheckedDistro::into_file -method nvx::host::sandbox::distro::CheckedDistro::path const nvx::host::sandbox::distro::DISTRO_POLICY_REVISION struct nvx::host::sandbox::distro::DistroBinding method nvx::host::sandbox::distro::DistroBinding::check -method nvx::host::sandbox::distro::DistroBinding::format -method nvx::host::sandbox::distro::DistroBinding::identity -method nvx::host::sandbox::distro::DistroBinding::image -method nvx::host::sandbox::distro::DistroBinding::import -method nvx::host::sandbox::distro::DistroBinding::manifest -method nvx::host::sandbox::distro::DistroBinding::policy method nvx::host::sandbox::distro::DistroBinding::verify enum nvx::host::sandbox::distro::DistroFormat variant nvx::host::sandbox::distro::DistroFormat::Erofs @@ -2267,18 +2257,10 @@ mod nvx::host::sandbox::scratch const nvx::host::sandbox::scratch::CLAIM_RELEASE_WAIT struct nvx::host::sandbox::scratch::CheckedScratch method nvx::host::sandbox::scratch::CheckedScratch::file -method nvx::host::sandbox::scratch::CheckedScratch::geometry -method nvx::host::sandbox::scratch::CheckedScratch::into_file -method nvx::host::sandbox::scratch::CheckedScratch::path const nvx::host::sandbox::scratch::SCRATCH_BINDING_VERSION struct nvx::host::sandbox::scratch::ScratchBinding method nvx::host::sandbox::scratch::ScratchBinding::bind method nvx::host::sandbox::scratch::ScratchBinding::check -method nvx::host::sandbox::scratch::ScratchBinding::device -method nvx::host::sandbox::scratch::ScratchBinding::file_id -method nvx::host::sandbox::scratch::ScratchBinding::kind -method nvx::host::sandbox::scratch::ScratchBinding::length -method nvx::host::sandbox::scratch::ScratchBinding::scheme mod nvx::host::seal enum nvx::host::seal::FileKind variant nvx::host::seal::FileKind::Block @@ -2289,12 +2271,10 @@ variant nvx::host::seal::FileKind::Other variant nvx::host::seal::FileKind::Regular variant nvx::host::seal::FileKind::Socket struct nvx::host::seal::FileSeal -method nvx::host::seal::FileSeal::changed method nvx::host::seal::FileSeal::device method nvx::host::seal::FileSeal::file_id method nvx::host::seal::FileSeal::kind method nvx::host::seal::FileSeal::length -method nvx::host::seal::FileSeal::modified method nvx::host::seal::FileSeal::of method nvx::host::seal::FileSeal::scheme const nvx::host::seal::SEAL_VERSION diff --git a/nvx-rs/public-api/windows.txt b/nvx-rs/public-api/windows.txt index 32b73143b..c0c08db40 100644 --- a/nvx-rs/public-api/windows.txt +++ b/nvx-rs/public-api/windows.txt @@ -853,19 +853,9 @@ method nvx::host::sandbox::SandboxMount::with_kind mod nvx::host::sandbox::distro const nvx::host::sandbox::distro::BINDING_VERSION struct nvx::host::sandbox::distro::CheckedDistro -method nvx::host::sandbox::distro::CheckedDistro::file -method nvx::host::sandbox::distro::CheckedDistro::geometry -method nvx::host::sandbox::distro::CheckedDistro::into_file -method nvx::host::sandbox::distro::CheckedDistro::path const nvx::host::sandbox::distro::DISTRO_POLICY_REVISION struct nvx::host::sandbox::distro::DistroBinding method nvx::host::sandbox::distro::DistroBinding::check -method nvx::host::sandbox::distro::DistroBinding::format -method nvx::host::sandbox::distro::DistroBinding::identity -method nvx::host::sandbox::distro::DistroBinding::image -method nvx::host::sandbox::distro::DistroBinding::import -method nvx::host::sandbox::distro::DistroBinding::manifest -method nvx::host::sandbox::distro::DistroBinding::policy method nvx::host::sandbox::distro::DistroBinding::verify enum nvx::host::sandbox::distro::DistroFormat variant nvx::host::sandbox::distro::DistroFormat::Erofs @@ -994,19 +984,11 @@ fn nvx::host::sandbox::require_file mod nvx::host::sandbox::scratch const nvx::host::sandbox::scratch::CLAIM_RELEASE_WAIT struct nvx::host::sandbox::scratch::CheckedScratch -method nvx::host::sandbox::scratch::CheckedScratch::file -method nvx::host::sandbox::scratch::CheckedScratch::geometry method nvx::host::sandbox::scratch::CheckedScratch::into_file -method nvx::host::sandbox::scratch::CheckedScratch::path const nvx::host::sandbox::scratch::SCRATCH_BINDING_VERSION struct nvx::host::sandbox::scratch::ScratchBinding method nvx::host::sandbox::scratch::ScratchBinding::bind method nvx::host::sandbox::scratch::ScratchBinding::check -method nvx::host::sandbox::scratch::ScratchBinding::device -method nvx::host::sandbox::scratch::ScratchBinding::file_id -method nvx::host::sandbox::scratch::ScratchBinding::kind -method nvx::host::sandbox::scratch::ScratchBinding::length -method nvx::host::sandbox::scratch::ScratchBinding::scheme mod nvx::host::seal enum nvx::host::seal::FileKind variant nvx::host::seal::FileKind::Block @@ -1017,12 +999,10 @@ variant nvx::host::seal::FileKind::Other variant nvx::host::seal::FileKind::Regular variant nvx::host::seal::FileKind::Socket struct nvx::host::seal::FileSeal -method nvx::host::seal::FileSeal::changed method nvx::host::seal::FileSeal::device method nvx::host::seal::FileSeal::file_id method nvx::host::seal::FileSeal::kind method nvx::host::seal::FileSeal::length -method nvx::host::seal::FileSeal::modified method nvx::host::seal::FileSeal::of method nvx::host::seal::FileSeal::scheme const nvx::host::seal::SEAL_VERSION diff --git a/nvx-rs/src/host/sandbox/distro.rs b/nvx-rs/src/host/sandbox/distro.rs index 1a2798914..b9df48e30 100644 --- a/nvx-rs/src/host/sandbox/distro.rs +++ b/nvx-rs/src/host/sandbox/distro.rs @@ -20,13 +20,11 @@ //! never reads, maps, or hashes either file. A missing or stale binding fails: no launch //! registers, verifies, or hashes a layer on its own. //! -//! A [`DistroBinding`] comes from explicit verification, or from a staging caller that verified -//! the layer itself and imports the binding with [`DistroBinding::import`], which does not hash -//! the layer again. It ties the [`FileSeal`] of the layer to the seal of its manifest, or to the -//! manifest's absence, to the revision and result of the distro policy, and to the layer's format -//! and the identity that the guest expects of it. Callers record bindings, as -//! `nvx-rs sandbox provision` records one in the sandbox's configuration, so their serde form is -//! part of the callers' state. +//! A [`DistroBinding`] comes from explicit verification. It ties the [`FileSeal`] of the layer to +//! the seal of its manifest, or to the manifest's absence, to the revision and result of the +//! distro policy, and to the layer's format and the identity that the guest expects of it. +//! Callers record bindings, as `nvx-rs sandbox provision` records one in the sandbox's +//! configuration, so their serde form is part of the callers' state. //! //! A seal detects accidental change, not deliberate tampering by the account that can write the //! sealed files, as [`seal`] explains. A caller therefore stages layers and their manifests @@ -42,10 +40,9 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use sha2::{Digest, Sha256}; -use super::geometry::BlockGeometry; use super::layer::SandboxLayer; use super::mount::os_error; -use super::{block_geometry, refused}; +use super::refused; use crate::error::{Error, Result}; use crate::host::artifacts::DISTRO_MANIFEST_SUFFIX; use crate::host::network::policy::MAX_JSON_DEPTH; @@ -80,8 +77,7 @@ impl Access<'static> { seal: &FileSeal::of, }; - /// The access of the staged check and of imports: [`seal::open_guarded`] and - /// [`FileSeal::of`]. + /// The access of the staged check: [`seal::open_guarded`] and [`FileSeal::of`]. pub(crate) const GUARDED: Self = Self { open: &seal::open_guarded, seal: &FileSeal::of, @@ -146,9 +142,8 @@ impl PolicyVerdict { /// The binding of a distro layer: the seals of the layer and of its manifest, or the manifest's /// absence, the policy verdict, and the layer's format and expected identity. /// -/// [`verify`](Self::verify) and [`import`](Self::import) make a binding, and -/// [`check`](Self::check) checks the layer against it before a launch. Its serde form, which -/// callers record, holds every field, each required: +/// [`verify`](Self::verify) makes a binding, and [`check`](Self::check) checks the layer against +/// it before a launch. Its serde form, which callers record, holds every field, each required: /// /// ```json /// {"version": 1, "format": "erofs", "identity": "11111111-1111-1111-1111-111111111111", @@ -184,53 +179,8 @@ pub struct DistroBinding { /// open of the layer shares it. Dropping it closes the file. #[derive(Debug)] pub struct CheckedDistro { - /// The checked layer. - file: File, - /// The path at which the check opened the layer. - path: PathBuf, - /// The layer's format, as the binding names it. - format: DistroFormat, -} - -impl CheckedDistro { - /// Returns the checked layer. - pub fn file(&self) -> &File { - &self.file - } - - /// Returns the path at which the check opened the layer. - pub fn path(&self) -> &Path { - &self.path - } - - /// Returns the block geometry that OpenVMM presents for the layer as the read-only `distro` - /// block device, read through the checked file, as OpenVMM reads it through its own open of - /// the layer: the layer's length, which must be a nonzero multiple of 512 bytes; logical - /// blocks of 512 bytes; and physical blocks of the I/O block size of the layer's filesystem, - /// its `st_blksize`, on Linux, of 4096 bytes on Windows, and of the logical block size on - /// other hosts, which must be a power of two of at least 512 bytes. - /// - /// It reads the file's metadata alone. A restore of a template presents its `distro` at the - /// geometry that the template recorded, the capacity of every clone's image, over an image - /// that may be shorter, so the image of a clone must be no longer than the template's - /// `distro`, with the same logical and physical block sizes. - /// - /// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) for a length or a - /// physical block size that OpenVMM refuses, and with [`ErrorKind::Io`](crate::ErrorKind::Io) - /// if the metadata cannot be read. - pub fn geometry(&self) -> Result { - let description = match self.format { - DistroFormat::Erofs => "distro EROFS layer", - DistroFormat::Gpt => "distro GPT image", - }; - block_geometry(&self.file, &self.path, description) - } - - /// Returns the checked layer, which the caller then holds: an owned descriptor on Linux, and - /// an owned handle on Windows, which it converts into an `OwnedFd` or an `OwnedHandle`. - pub fn into_file(self) -> File { - self.file - } + /// The checked layer, which stays open until the caller drops it. + _file: File, } impl DistroBinding { @@ -256,22 +206,6 @@ impl DistroBinding { verify_with(layer, Access::READ) } - /// Returns the binding of the distro layer `layer`, staged and judged by its caller, which - /// verified the layer itself, without reading the layer or its manifest. - /// - /// The binding holds the seals of the layer and of its manifest, if one sits beside it, and - /// `verdict`. A caller that imports a binding vouches for the layer and its manifest as - /// [`verify`](Self::verify) would; it must therefore be authorized to stage layers, which - /// NVX leaves to it. Fails with - /// [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) for a path without a name, with - /// [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) if the layer or its manifest is - /// not a regular file, and with [`ErrorKind::Io`](crate::ErrorKind::Io) if either cannot be - /// opened or sealed, as for a manifest path whose lookup fails for another reason than a - /// missing file, which the import does not take for an absent manifest. - pub fn import(layer: &SandboxLayer, verdict: PolicyVerdict) -> Result { - import_with(layer, verdict, Access::GUARDED) - } - /// Checks the distro layer `layer` against this binding from metadata alone, and returns the /// layer, open, as the check found it. /// @@ -294,32 +228,6 @@ impl DistroBinding { self.check_with(layer, policy_revision, Access::GUARDED) } - /// Returns the layer's format. - pub fn format(&self) -> DistroFormat { - self.format - } - - /// Returns the identity that the guest expects of the layer: the UUID of its EROFS - /// filesystem, or the GUID of its GPT disk. - pub fn identity(&self) -> &str { - &self.identity - } - - /// Returns the seal of the layer. - pub fn image(&self) -> &FileSeal { - &self.image - } - - /// Returns the seal of the layer's manifest, or `None` for a layer without one. - pub fn manifest(&self) -> Option<&FileSeal> { - self.manifest.as_ref() - } - - /// Returns the policy verdict. - pub fn policy(&self) -> PolicyVerdict { - self.policy - } - /// Returns the binding of `layer`, with the seals of its image and manifest and `policy`. fn new( layer: &SandboxLayer, @@ -424,11 +332,7 @@ impl DistroBinding { } }, } - Ok(CheckedDistro { - file, - path: path.to_path_buf(), - format: self.format, - }) + Ok(CheckedDistro { _file: file }) } } @@ -509,19 +413,6 @@ pub(crate) fn verify_with(layer: &SandboxLayer, access: Access<'_>) -> Result, -) -> Result { - let path = layer.path(); - let manifest = manifest_path(path)?; - let image = seal_regular(path, access, &layer_description(layer))?; - let manifest = seal_if_present(&manifest, access, "sandbox distro manifest")?; - Ok(DistroBinding::new(layer, image, manifest, verdict)) -} - /// Returns how messages name the image of `layer`: `distro EROFS layer` or `distro GPT image`. fn layer_description(layer: &SandboxLayer) -> String { match layer.format() { diff --git a/nvx-rs/src/host/sandbox/distro/tests.rs b/nvx-rs/src/host/sandbox/distro/tests.rs index 15b4b88d6..0e53a079c 100644 --- a/nvx-rs/src/host/sandbox/distro/tests.rs +++ b/nvx-rs/src/host/sandbox/distro/tests.rs @@ -1,5 +1,4 @@ -//! Tests of distro bindings: explicit verification, imports, and the staged check, which reads no -//! content. +//! Tests of distro bindings: explicit verification, and the staged check, which reads no content. use std::cell::Cell; use std::fs::{self, OpenOptions}; @@ -9,8 +8,9 @@ use std::time::Duration; use super::*; use crate::ErrorKind; +use crate::host::config::BlockGeometry; use crate::host::paths; -use crate::host::sandbox::LayerRole; +use crate::host::sandbox::{LayerRole, block_geometry}; use crate::host::seal::open_read; use crate::host::seal::tests::open_metadata_only; @@ -158,18 +158,18 @@ fn verification_binds_the_layer_its_manifest_and_the_policy() { for with_manifest in [true, false] { let staged = Staged::new(with_manifest); let binding = DistroBinding::verify(&staged.layer()).unwrap(); - assert_eq!(binding.format(), DistroFormat::Erofs); - assert_eq!(binding.identity(), UUID); + assert_eq!(binding.format, DistroFormat::Erofs); + assert_eq!(binding.identity, UUID); assert_eq!( - binding.policy(), + binding.policy, PolicyVerdict { revision: DISTRO_POLICY_REVISION, result: PolicyResult::Accepted, } ); - assert_eq!(binding.image(), &seal_of(&staged.image())); + assert_eq!(binding.image, seal_of(&staged.image())); let manifest = with_manifest.then(|| seal_of(&staged.manifest())); - assert_eq!(binding.manifest(), manifest.as_ref()); + assert_eq!(binding.manifest, manifest); binding .check(&staged.layer(), DISTRO_POLICY_REVISION) .unwrap(); @@ -186,7 +186,7 @@ fn bindings_round_trip_through_their_serde_form() { assert_eq!(value["identity"], UUID); assert_eq!( value["image"], - serde_json::to_value(binding.image()).unwrap() + serde_json::to_value(&binding.image).unwrap() ); // The absence of a manifest is explicit. assert_eq!(value.get("manifest"), Some(&serde_json::Value::Null)); @@ -216,7 +216,7 @@ fn bindings_round_trip_through_their_serde_form() { assert_eq!( serde_json::from_value::(rejected) .unwrap() - .policy() + .policy .result, PolicyResult::Rejected ); @@ -227,16 +227,16 @@ fn the_staged_check_reads_no_content() { let staged = Staged::new(true); let layer = staged.layer(); let binding = DistroBinding::verify(&layer).unwrap(); - assert!(binding.manifest().is_some()); + assert!(binding.manifest.is_some()); // On a host that lets the check open the layer and its manifest and read their metadata, but // fails every read and mapping of their content, the check passes, and returns the layer. let checked = binding .check_with(&layer, DISTRO_POLICY_REVISION, METADATA_ONLY) .unwrap(); - assert_eq!(&FileSeal::of(checked.file()).unwrap(), binding.image()); + assert_eq!(FileSeal::of(&checked._file).unwrap(), binding.image); let mut byte = [0]; - assert!(checked.into_file().read(&mut byte).is_err()); + assert!((&checked._file).read(&mut byte).is_err()); // Verification, the positive control, reads both files there: the manifest first, which it // fails to read... @@ -301,27 +301,32 @@ fn checked_layers_present_the_geometry_that_openvmm_reads_through_them() { .unwrap(); }; let check = |layer: &SandboxLayer| { - import_with(layer, PolicyVerdict::ACCEPTED, METADATA_ONLY) + verify_with(layer, METADATA_ONLY) .unwrap() .check_with(layer, DISTRO_POLICY_REVISION, METADATA_ONLY) .unwrap() }; + // The geometry that OpenVMM presents for the layer, read through the checked file. + let geometry = |layer: &SandboxLayer, checked: &CheckedDistro| { + block_geometry(&checked._file, layer.path(), &layer_description(layer)) + }; resize(8192); - let checked = check(&staged.layer()); - let geometry = BlockGeometry { + let layer = staged.layer(); + let checked = check(&layer); + let expected = BlockGeometry { length: 8192, logical_block_size: 512, physical_block_size: physical_block_size(&image), }; // It reads the metadata of the checked file alone, which reads nothing. - assert_eq!(checked.geometry().unwrap(), geometry); + assert_eq!(geometry(&layer, &checked).unwrap(), expected); // The geometry is the checked file's, whatever later takes its path. #[cfg(target_os = "linux")] { let replacement = staged.other("replacement.erofs"); fs::write(&replacement, [0; 4096]).unwrap(); fs::rename(&replacement, &image).unwrap(); - assert_eq!(checked.geometry().unwrap(), geometry); + assert_eq!(geometry(&layer, &checked).unwrap(), expected); } drop(checked); // OpenVMM attaches block devices of nonzero multiples of 512 bytes alone. @@ -333,7 +338,7 @@ fn checked_layers_present_the_geometry_that_openvmm_reads_through_them() { for length in [0, 8191] { resize(length); assert_eq!( - failure(check(&layer).geometry()), + failure(geometry(&layer, &check(&layer))), ( ErrorKind::Unsupported, format!( @@ -379,12 +384,12 @@ fn layers_present_the_block_size_of_their_filesystem() { open: &open, seal: &FileSeal::of, }; - let checked = import_with(&staged.layer(), PolicyVerdict::ACCEPTED, access) + let checked = verify_with(&staged.layer(), access) .unwrap() .check_with(&staged.layer(), DISTRO_POLICY_REVISION, access) .unwrap(); assert_eq!( - checked.geometry().unwrap(), + block_geometry(&checked._file, &image, "distro EROFS layer").unwrap(), BlockGeometry { length: HUGE_PAGE, logical_block_size: 512, @@ -552,15 +557,13 @@ fn the_staged_check_fails_without_reading_for_another_policy_or_layer() { ); // A verdict that rejects the layer passes no check. - let rejected = import_with( - &layer, - PolicyVerdict { + let rejected = DistroBinding { + policy: PolicyVerdict { revision: DISTRO_POLICY_REVISION, result: PolicyResult::Rejected, }, - METADATA_ONLY, - ) - .unwrap(); + ..binding.clone() + }; assert_eq!( failure(rejected.check_with(&layer, DISTRO_POLICY_REVISION, METADATA_ONLY)), ( @@ -666,8 +669,8 @@ fn the_checked_file_is_the_object_that_the_check_compared() { // The caller owns the file, which is still the object that the check compared, with its // content, whatever has its path now, and which processes that the caller launches do not // inherit. On Linux, the replacement unlinked the object, which may move its change time. - let mut file = checked.into_file(); - let difference = binding.image().difference(&FileSeal::of(&file).unwrap()); + let mut file = checked._file; + let difference = binding.image.difference(&FileSeal::of(&file).unwrap()); assert!( difference.is_none() || (cfg!(target_os = "linux") && difference == Some("change time")), "{difference:?}" @@ -689,66 +692,18 @@ fn the_checked_file_is_the_object_that_the_check_compared() { ); } -#[test] -fn imports_bind_a_staged_layer_without_reading_it() { - let staged = Staged::new(true); - let layer = staged.layer(); - // The staging caller vouches for the layer and its manifest, which the import does not read: - // even a manifest that verification refuses. - fs::write(staged.manifest(), b"not JSON").unwrap(); - let binding = import_with(&layer, PolicyVerdict::ACCEPTED, METADATA_ONLY).unwrap(); - assert_eq!(binding.image(), &seal_of(&staged.image())); - assert_eq!(binding.manifest(), Some(&seal_of(&staged.manifest()))); - assert_eq!(binding.policy(), PolicyVerdict::ACCEPTED); - assert_eq!(binding.identity(), UUID); - binding.check(&layer, DISTRO_POLICY_REVISION).unwrap(); - - // A verdict of the caller's own policy passes the checks of that policy's revision alone. - let own = PolicyVerdict { - revision: 7, - result: PolicyResult::Accepted, - }; - let binding = DistroBinding::import(&layer, own).unwrap(); - binding.check(&layer, 7).unwrap(); - assert_eq!( - failure(binding.check(&layer, DISTRO_POLICY_REVISION)).0, - ErrorKind::Unavailable - ); - - // Without a manifest, the import binds its absence. - fs::remove_file(staged.manifest()).unwrap(); - let binding = DistroBinding::import(&layer, PolicyVerdict::ACCEPTED).unwrap(); - assert_eq!(binding.manifest(), None); - binding.check(&layer, DISTRO_POLICY_REVISION).unwrap(); - - // It binds regular files alone. - fs::remove_file(staged.image()).unwrap(); - fs::create_dir(staged.image()).unwrap(); - assert_eq!( - failure(DistroBinding::import(&layer, PolicyVerdict::ACCEPTED)), - ( - ErrorKind::Unavailable, - format!("distro EROFS layer not found: {}", shown(&staged.image())) - ) - ); -} - #[test] fn gpt_disks_bind_by_their_disk_guid() { let staged = Staged::new(false); let image = shown(&staged.image()); let gpt = SandboxLayer::gpt(LayerRole::Distro, staged.image().to_str().unwrap(), UUID); - // Verification of a disk without a manifest reads nothing, and binds what an import binds. + // Verification of a disk without a manifest reads nothing. let binding = verify_with(&gpt, METADATA_ONLY).unwrap(); assert_eq!( - (binding.format(), binding.identity()), + (binding.format, binding.identity.as_str()), (DistroFormat::Gpt, UUID) ); - assert_eq!( - import_with(&gpt, PolicyVerdict::ACCEPTED, METADATA_ONLY).unwrap(), - binding - ); let value = serde_json::to_value(&binding).unwrap(); assert_eq!(value["format"], "gpt"); assert_eq!( @@ -760,7 +715,7 @@ fn gpt_disks_bind_by_their_disk_guid() { let checked = binding .check_with(&gpt, DISTRO_POLICY_REVISION, METADATA_ONLY) .unwrap(); - assert_eq!(&FileSeal::of(checked.file()).unwrap(), binding.image()); + assert_eq!(FileSeal::of(&checked._file).unwrap(), binding.image); // A binding of one format checks no image of the other, even at its path and identity. let erofs = staged.layer(); @@ -773,7 +728,7 @@ fn gpt_disks_bind_by_their_disk_guid() { format!( "the distro binding of {image} names the {} layer with identity {UUID}, not \ the {} layer with identity {UUID}", - bound.format(), + bound.format, launched.format() ) ) @@ -783,7 +738,7 @@ fn gpt_disks_bind_by_their_disk_guid() { // Messages name a missing disk a GPT image. fs::remove_file(staged.image()).unwrap(); assert_eq!( - failure(DistroBinding::import(&gpt, PolicyVerdict::ACCEPTED)), + failure(DistroBinding::verify(&gpt)), ( ErrorKind::Unavailable, format!("distro GPT image not found: {image}") @@ -806,26 +761,23 @@ fn link_to_itself(path: &Path) -> bool { fn a_manifest_path_whose_lookup_fails_is_no_absent_manifest_for_a_binding() { // Verification finds a manifest as `Path.exists` does, which counts a path whose lookup fails // as none. The binding of the manifest's absence holds only while nothing is at its path, so - // the staged check refuses such a path, without reading anything, and so does an import. + // the staged check refuses such a path, without reading anything. let staged = Staged::new(false); let layer = staged.layer(); let binding = verify_with(&layer, METADATA_ONLY).unwrap(); - assert_eq!(binding.manifest(), None); + assert_eq!(binding.manifest, None); if !link_to_itself(&staged.manifest()) { eprintln!("skipped: this host does not let the test create a symbolic link"); return; } assert_eq!(verify_with(&layer, METADATA_ONLY).unwrap(), binding); - for (kind, message) in [ - failure(binding.check_with(&layer, DISTRO_POLICY_REVISION, METADATA_ONLY)), - failure(import_with(&layer, PolicyVerdict::ACCEPTED, METADATA_ONLY)), - ] { - assert_eq!(kind, ErrorKind::Io, "{message}"); - assert!( - message.starts_with(&format!("{:?}: ", shown(&staged.manifest()))), - "{message}" - ); - } + let (kind, message) = + failure(binding.check_with(&layer, DISTRO_POLICY_REVISION, METADATA_ONLY)); + assert_eq!(kind, ErrorKind::Io, "{message}"); + assert!( + message.starts_with(&format!("{:?}: ", shown(&staged.manifest()))), + "{message}" + ); // Once nothing is at the path, the binding holds again. fs::remove_file(staged.manifest()).unwrap(); binding diff --git a/nvx-rs/src/host/sandbox/mod.rs b/nvx-rs/src/host/sandbox/mod.rs index 28055573c..33e6b602a 100644 --- a/nvx-rs/src/host/sandbox/mod.rs +++ b/nvx-rs/src/host/sandbox/mod.rs @@ -17,10 +17,9 @@ //! layer and its manifest, and which a launch checks from metadata alone. //! - [`scratch`]: the binding of the scratch image, its identity, kind, and length, which a //! launch checks from metadata alone, and the claim of the image by one sandbox at a time. -//! - The block geometry that OpenVMM presents for each checked file, its length and its logical -//! and physical block sizes, which [`CheckedDistro::geometry`](distro::CheckedDistro::geometry) -//! and [`CheckedScratch::geometry`](scratch::CheckedScratch::geometry) read through the file, -//! as OpenVMM reads it. +//! - The block geometry that OpenVMM presents for a block device, its length and its logical and +//! physical block sizes, which the crate reads through an open file of the device's image, as +//! OpenVMM reads it. //! //! Workload IDs are ASCII decimal integers in their documented ranges. Layer UUIDs use their //! canonical lowercase hyphenated text. Host paths use the host's native flavor. @@ -131,8 +130,11 @@ const WINDOWS_PHYSICAL_BLOCK_SIZE: u32 = 4096; /// Returns the block geometry that OpenVMM presents for a sandbox block device whose file `file` /// holds open, which messages call `description` at `path`, read through `file` as OpenVMM reads -/// it through its own open of the file, as -/// [`CheckedDistro::geometry`](distro::CheckedDistro::geometry) describes. +/// it through its own open of the file: the file's length, which must be a nonzero multiple of +/// 512 bytes; logical blocks of 512 bytes; and physical blocks of the I/O block size of the file's +/// filesystem, its `st_blksize`, on Linux, of 4096 bytes on Windows, and of the logical block size +/// on other hosts, which must be a power of two of at least 512 bytes. It reads the file's +/// metadata alone. /// /// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) for a length or a physical /// block size that OpenVMM refuses, and with [`ErrorKind::Io`](crate::ErrorKind::Io) if the file's diff --git a/nvx-rs/src/host/sandbox/options/tests.rs b/nvx-rs/src/host/sandbox/options/tests.rs index e54f01680..829ea781a 100644 --- a/nvx-rs/src/host/sandbox/options/tests.rs +++ b/nvx-rs/src/host/sandbox/options/tests.rs @@ -204,9 +204,10 @@ fn distro_manifest_binds_the_layer() { }); layer.manifest(&valid.to_string()); let binding = layer.preflight("/bin/sh").unwrap(); - assert_eq!(binding.identity(), "11111111-1111-1111-1111-111111111111"); - assert_eq!(binding.format(), DistroFormat::Erofs); - assert!(binding.manifest().is_some()); + let recorded = serde_json::to_value(&binding).unwrap(); + assert_eq!(recorded["identity"], "11111111-1111-1111-1111-111111111111"); + assert_eq!(recorded["format"], DistroFormat::Erofs.as_str()); + assert!(recorded["manifest"].is_object()); binding .check(&layer.layer(), DISTRO_POLICY_REVISION) .unwrap(); @@ -378,7 +379,7 @@ fn the_entrypoint_check_needs_no_manifest_and_precedes_the_distro_verification() // A layer without a manifest passes the distro verification unread, and its binding records // the manifest's absence, but the entrypoint check still refuses systemd. let binding = layer.preflight("/sbin/init").unwrap(); - assert_eq!(binding.manifest(), None); + assert!(serde_json::to_value(&binding).unwrap()["manifest"].is_null()); assert_eq!( message(layer.preflight("/usr/lib/systemd/systemd")), "systemd entrypoints are unsupported by the sandbox security profile" @@ -417,7 +418,10 @@ fn sandbox_rejects_systemd_from_bound_distro_metadata() { &fs::File::open(layer.path("distro.erofs.manifest.json")).unwrap(), ) .unwrap(); - assert_eq!(binding.manifest(), Some(&sealed)); + assert_eq!( + serde_json::to_value(&binding).unwrap()["manifest"], + serde_json::to_value(&sealed).unwrap() + ); for (format, artifact, sha256) in [ ("\"1\"", "\"distro.erofs\"", sha256.as_str()), ("1.0", "\"distro.erofs\"", &sha256), diff --git a/nvx-rs/src/host/sandbox/scratch.rs b/nvx-rs/src/host/sandbox/scratch.rs index 0c182328b..e1099369a 100644 --- a/nvx-rs/src/host/sandbox/scratch.rs +++ b/nvx-rs/src/host/sandbox/scratch.rs @@ -54,9 +54,8 @@ use std::time::{Duration, Instant}; use serde::{Deserialize, Serialize}; -use super::geometry::BlockGeometry; use super::mount::os_error; -use super::{block_geometry, refused}; +use super::refused; use crate::error::{Error, ErrorCode, Result}; use crate::host::paths; use crate::host::seal::{FileId, FileKind, FileSeal, SealScheme}; @@ -145,43 +144,19 @@ pub struct CheckedScratch { ) )] claim: File, - /// The path at which the check opened the image. - path: PathBuf, } impl CheckedScratch { - /// Returns the checked image: on Linux, the image open for reading and writing, and on Windows - /// its guard. + /// Returns the checked image, open for reading and writing, whose open file description holds + /// the claim on Linux, which a VMM that inherits a copy of it keeps. + #[cfg(not(windows))] pub fn file(&self) -> &File { &self.file } - /// Returns the path at which the check opened the image. - pub fn path(&self) -> &Path { - &self.path - } - - /// Returns the block geometry that OpenVMM presents for the image as the `scratch` block - /// device, read through the checked file, as - /// [`CheckedDistro::geometry`](super::distro::CheckedDistro::geometry) reads that of a distro - /// layer: the image's length, logical blocks of 512 bytes, and physical blocks of the I/O - /// block size of the image's filesystem on Linux, of 4096 bytes on Windows, and of the logical - /// block size on other hosts. On Windows, the checked file is the image's guard, which may - /// read the image's metadata. - /// - /// It reads the file's metadata alone, never the image's content. The scratch image of a - /// clone must present the geometry of its template's scratch, its length included. - /// - /// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) for a length or a - /// physical block size that OpenVMM refuses, and with [`ErrorKind::Io`](crate::ErrorKind::Io) - /// if the metadata cannot be read. - pub fn geometry(&self) -> Result { - block_geometry(&self.file, &self.path, DESCRIPTION) - } - - /// Returns the checked image, which the caller then holds with its claim: an owned descriptor - /// on Linux, and on Windows an owned handle, the image's guard, which then holds the claim + /// Returns the image's guard, an owned handle, which the caller then holds with the claim /// alone, since this closes the claim's handle. + #[cfg(windows)] pub fn into_file(self) -> File { self.file } @@ -228,31 +203,6 @@ impl ScratchBinding { self.check_with(&normalized(path)?, deadline, Access::WRITABLE) } - /// Returns the host that took the binding. - pub fn scheme(&self) -> SealScheme { - self.scheme - } - - /// Returns the device, or on Windows the volume serial number. - pub fn device(&self) -> u64 { - self.device - } - - /// Returns the inode, or on Windows the file ID. - pub fn file_id(&self) -> u128 { - self.file_id.0 - } - - /// Returns the kind of file. - pub fn kind(&self) -> FileKind { - self.kind - } - - /// Returns the length in bytes. - pub fn length(&self) -> u64 { - self.length - } - /// Returns the binding of the image that `seal` seals. fn of(seal: &FileSeal) -> Self { Self { @@ -303,7 +253,6 @@ impl ScratchBinding { file, #[cfg(windows)] claim, - path: path.to_path_buf(), }) } diff --git a/nvx-rs/src/host/sandbox/scratch/tests.rs b/nvx-rs/src/host/sandbox/scratch/tests.rs index 611110a12..83f18f513 100644 --- a/nvx-rs/src/host/sandbox/scratch/tests.rs +++ b/nvx-rs/src/host/sandbox/scratch/tests.rs @@ -8,6 +8,8 @@ use std::time::SystemTime; use super::*; use crate::ErrorKind; +use crate::host::config::BlockGeometry; +use crate::host::sandbox::block_geometry; /// The content of the staged scratch images. const CONTENT: &[u8] = b"scratch image\n"; @@ -151,13 +153,13 @@ fn binds_record_the_image_without_reading_it() { let staged = Staged::new(); let binding = staged.bind(); let seal = FileSeal::of(&File::open(staged.image()).unwrap()).unwrap(); - assert_eq!(binding.scheme(), seal.scheme()); + assert_eq!(binding.scheme, seal.scheme()); assert_eq!( - (binding.device(), binding.file_id()), + (binding.device, binding.file_id.0), (seal.device(), seal.file_id()) ); - assert_eq!(binding.kind(), FileKind::Regular); - assert_eq!(binding.length(), CONTENT.len() as u64); + assert_eq!(binding.kind, FileKind::Regular); + assert_eq!(binding.length, CONTENT.len() as u64); assert_eq!(ScratchBinding::bind(&staged.image()).unwrap(), binding); // The positive control: what the access of the test opens refuses every read. let mut byte = [0]; @@ -181,7 +183,7 @@ fn bindings_round_trip_through_their_serde_form() { value["scheme"], if cfg!(windows) { "windows" } else { "linux" } ); - assert_eq!(value["fileId"], format!("{:032x}", binding.file_id())); + assert_eq!(value["fileId"], format!("{:032x}", binding.file_id.0)); assert_eq!(value["kind"], "regular"); assert_eq!(value["length"], CONTENT.len()); assert_eq!( @@ -246,17 +248,16 @@ fn checks_claim_the_image_without_reading_it() { let staged = Staged::new(); let binding = staged.bind(); let checked = staged.check(&binding, Instant::now()).unwrap(); - assert_eq!(checked.path().to_str().unwrap(), shown(&staged.image())); - let seal = FileSeal::of(checked.file()).unwrap(); + let seal = FileSeal::of(&checked.file).unwrap(); assert_eq!( (seal.device(), seal.file_id()), - (binding.device(), binding.file_id()) + (binding.device, binding.file_id.0) ); // What the check holds reads nothing, and processes that this one launches do not inherit // it. let mut byte = [0]; - assert!(checked.file().read(&mut byte).is_err()); - assert!(!inheritable(checked.file())); + assert!((&checked.file).read(&mut byte).is_err()); + assert!(!inheritable(&checked.file)); #[cfg(windows)] { assert!((&checked.claim).read(&mut byte).is_err()); @@ -324,12 +325,15 @@ fn checked_images_present_the_geometry_that_openvmm_reads_through_them() { .set_len(length) .unwrap(); }; + // The geometry that OpenVMM presents for the image, read through what the check holds. + let geometry = + |checked: &CheckedScratch| block_geometry(&checked.file, &staged.normalized(), DESCRIPTION); resize(1 << 20); let binding = staged.bind(); let checked = staged.claim(&binding); // It reads the metadata of what the check holds, which reads nothing, while the claim lasts. assert_eq!( - checked.geometry().unwrap(), + geometry(&checked).unwrap(), BlockGeometry { length: 1 << 20, logical_block_size: 512, @@ -346,7 +350,7 @@ fn checked_images_present_the_geometry_that_openvmm_reads_through_them() { resize(length); let binding = staged.bind(); assert_eq!( - failure(staged.claim(&binding).geometry()), + failure(geometry(&staged.claim(&binding))), ( ErrorKind::Unsupported, format!( @@ -417,7 +421,7 @@ fn a_check_that_waited_for_a_claim_compares_the_image_again() { let staged = Staged::new(); let binding = staged.bind(); #[cfg(target_os = "linux")] - let holder = staged.check(&binding, Instant::now()).unwrap().into_file(); + let holder = staged.check(&binding, Instant::now()).unwrap().file; #[cfg(windows)] let holder = OpenOptions::new() .read(true) @@ -472,12 +476,15 @@ fn the_checked_image_is_the_object_that_the_check_compared() { // What the caller holds is still the object that the check compared, whatever has its path // now. - let seal = FileSeal::of(checked.file()).unwrap(); + let seal = FileSeal::of(&checked.file).unwrap(); assert_eq!( (seal.device(), seal.file_id(), seal.kind()), - (binding.device(), binding.file_id(), FileKind::Regular) + (binding.device, binding.file_id.0, FileKind::Regular) ); + #[cfg(windows)] let file = checked.into_file(); + #[cfg(not(windows))] + let file = checked.file; assert!(!inheritable(&file)); drop(file); diff --git a/nvx-rs/src/host/seal.rs b/nvx-rs/src/host/seal.rs index 3bc05e470..872899994 100644 --- a/nvx-rs/src/host/seal.rs +++ b/nvx-rs/src/host/seal.rs @@ -150,16 +150,6 @@ impl FileSeal { self.length } - /// Returns the modification time. - pub fn modified(&self) -> SealTime { - self.modified - } - - /// Returns the change time on Linux, and `None` on Windows. - pub fn changed(&self) -> Option { - self.changed - } - /// Returns what differs first between this seal and `other`, in the order version, scheme, /// identity, kind, length, modification time, and change time, or `None` if they are equal. pub(crate) fn difference(&self, other: &Self) -> Option<&'static str> { diff --git a/nvx-rs/src/host/seal/tests.rs b/nvx-rs/src/host/seal/tests.rs index f5a75ba2f..c882c541f 100644 --- a/nvx-rs/src/host/seal/tests.rs +++ b/nvx-rs/src/host/seal/tests.rs @@ -88,19 +88,19 @@ fn a_seal_holds_the_identity_kind_length_and_times_of_an_open_file() { assert_eq!(seal_of(&link), seal_of(&other)); assert_ne!(seal_of(&other).file_id(), seal.file_id()); let metadata = fs::metadata(&path).unwrap(); - assert_eq!(seal.modified(), seal_time(metadata.modified().unwrap())); + assert_eq!(seal.modified, seal_time(metadata.modified().unwrap())); #[cfg(target_os = "linux")] { use std::os::unix::fs::MetadataExt; - let changed = seal.changed().unwrap(); + let changed = seal.changed.unwrap(); assert_eq!( (changed.seconds, i64::from(changed.nanoseconds)), (metadata.ctime(), metadata.ctime_nsec()) ); } #[cfg(windows)] - assert_eq!(seal.changed(), None); + assert_eq!(seal.changed, None); // Every open of the unchanged file seals it alike, whatever its access. assert_eq!(FileSeal::of(&open_guarded(&path).unwrap()).unwrap(), seal); assert_eq!(FileSeal::of(&open_read(&path).unwrap()).unwrap(), seal); @@ -164,7 +164,7 @@ fn seals_differ_when_their_file_changes() { let touched = seal_of(&path); assert_eq!(appended.difference(&touched), Some("modification time")); assert_eq!( - touched.modified(), + touched.modified, seal_time(modified + Duration::from_secs(1)) ); assert_eq!(touched.difference(&touched.clone()), None); diff --git a/nvx-rs/tests/security_checklist.rs b/nvx-rs/tests/security_checklist.rs index 94ad4a916..f486d918a 100644 --- a/nvx-rs/tests/security_checklist.rs +++ b/nvx-rs/tests/security_checklist.rs @@ -22,7 +22,7 @@ use nvx::host::filesystem::Owner; use nvx::host::filesystem::{self, ExportRequest, Mapping, ReservedTargets, Target}; use nvx::host::process::{BootId, ProcessRef}; use nvx::host::runtime::{ArtifactIdentity, LaunchOptions, Runtime, RuntimeIdentity}; -use nvx::host::sandbox::distro::{DISTRO_POLICY_REVISION, DistroBinding, PolicyVerdict}; +use nvx::host::sandbox::distro::{DISTRO_POLICY_REVISION, DistroBinding}; use nvx::host::sandbox::layer::{LayerRole, SandboxLayer}; use nvx::host::sandbox::scratch::{CLAIM_RELEASE_WAIT, ScratchBinding}; use nvx::host::seal::FileSeal; @@ -155,7 +155,7 @@ fn a_staged_image_that_was_replaced_fails_its_check() { path.to_str().unwrap(), "01234567-89ab-cdef-0123-456789abcdef", ); - let binding = DistroBinding::import(&layer, PolicyVerdict::ACCEPTED).unwrap(); + let binding = DistroBinding::verify(&layer).unwrap(); drop(binding.check(&layer, DISTRO_POLICY_REVISION).unwrap()); fs::rename(&path, images.path().join("original.erofs")).unwrap(); fs::write(&path, b"layer").unwrap();