From f319bd0b5c287294ebe869e29939e79869a67418 Mon Sep 17 00:00:00 2001 From: Pedro Henrique Penna Date: Wed, 11 Nov 2026 19:25:38 -0800 Subject: [PATCH] host: keep vm and the CLI's sandbox lifecycle off the old store Code that stays reaches the sandbox store, the instance layer and the store-coupled supervisor, which SL6.1 deletes: host::vm::create draws each launch's capability with host::instance::generate_capability, the CLI's stop of a sandbox reports its outcome as the supervisor's StopOutcome, and the CLI's provision creates the state directory with the sandbox store's SandboxStore::create. generate_capability moves to host::vm, crate-private, where create calls it; host::instance's public generate_capability calls it until SL6.1 deletes the instance. The CLI's stop takes a StopOutcome of its own, with the same three outcomes. create_state_directory creates the directory with the CLI's own StateDir::create, which provision's lock already uses: both create it private to the current user through platform::create_store_dir, refuse one that is not, and fail with the same codes and messages. SandboxStore::create's resolution of a relative path changed nothing there, since provision hands it the state directory that it already resolved. The managed-lifecycle test and the live restore test draw their own random capability, and the stop tests take the CLI's StopOutcome. Nothing else changes. Part of #1120 (SL6.0e). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/host/cli/sandbox/lifecycle/directory.rs | 16 +++++++--------- nvx-rs/src/host/cli/sandbox/lifecycle/stop.rs | 13 ++++++++++++- nvx-rs/src/host/cli/sandbox/lifecycle/tests.rs | 3 +-- nvx-rs/src/host/instance/mod.rs | 8 +------- nvx-rs/src/host/restore/tests/live_init.rs | 6 +++--- nvx-rs/src/host/vm.rs | 13 ++++++++++++- nvx-rs/tests/managed_lifecycle.rs | 4 ++-- 7 files changed, 38 insertions(+), 25 deletions(-) diff --git a/nvx-rs/src/host/cli/sandbox/lifecycle/directory.rs b/nvx-rs/src/host/cli/sandbox/lifecycle/directory.rs index 4bb0077b9..feac1ab22 100644 --- a/nvx-rs/src/host/cli/sandbox/lifecycle/directory.rs +++ b/nvx-rs/src/host/cli/sandbox/lifecycle/directory.rs @@ -3,10 +3,9 @@ use std::path::{Path, PathBuf}; use crate::ErrorCode; -use crate::host::cli::sandbox::error_with_cause; +use crate::host::cli::store::StateDir; use crate::host::cli::{Exit, Result, io_error}; use crate::host::paths; -use crate::host::store::SandboxStore; /// Returns the error for an I/O error that an operation on `path` met. pub(super) fn os_error(error: &std::io::Error, path: &Path) -> Exit { @@ -57,10 +56,11 @@ fn parent(path: &Path) -> Option<&Path> { } /// Creates the state directory at `path`, as provision does. An existing directory stays as it is, -/// and the store then refuses one that is not private to the current user; any other existing -/// path fails as creating a directory fails for a path that exists. A new directory is created as -/// the store creates it, private to the current user, below parents that are created as needed, -/// even when an existing one goes meanwhile: nothing else creates the directory itself. +/// and the CLI's store then refuses one that is not private to the current user; any other +/// existing path fails as creating a directory fails for a path that exists. A new directory is +/// created as [`StateDir::create`] creates it, private to the current user, below parents that +/// are created as needed, even when an existing one goes meanwhile: nothing else creates the +/// directory itself. pub(crate) fn create_state_directory(path: &Path) -> Result { if path.is_dir() { return Ok(()); @@ -71,9 +71,7 @@ pub(crate) fn create_state_directory(path: &Path) -> Result { if let Some(parent) = parent(path) { make_directories(parent, 0o777)?; } - SandboxStore::create(path) - .map(drop) - .map_err(error_with_cause) + StateDir::create(path).map(drop) } /// Returns the error with which creating a directory fails for a path that exists. diff --git a/nvx-rs/src/host/cli/sandbox/lifecycle/stop.rs b/nvx-rs/src/host/cli/sandbox/lifecycle/stop.rs index c0a3f5460..3ee79e70e 100644 --- a/nvx-rs/src/host/cli/sandbox/lifecycle/stop.rs +++ b/nvx-rs/src/host/cli/sandbox/lifecycle/stop.rs @@ -17,9 +17,20 @@ use crate::host::cli::{Exit, Result, check_interrupt, output}; use crate::host::process::Process; use crate::host::runtime::IdentityPart; use crate::host::snapshot::OPENVMM_DIR; -use crate::host::supervisor::StopOutcome; use crate::host::vm::{self, SavedVm, VmDir, VmOutputs}; +/// How the stop of a sandbox's OpenVMM ended. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum StopOutcome { + /// OpenVMM exited without a forced termination: by the end of its graceful stop, or before + /// a termination began. + Graceful, + /// A forced termination through OpenVMM's verified process ended it within its bound. + Forced, + /// OpenVMM still runs: it did not exit within the bound of its stop or termination. + TimedOut, +} + /// Stops a running sandbox, and checks the outcome report that its OpenVMM wrote, unless another /// runtime identity than the artifacts' started the VM. /// diff --git a/nvx-rs/src/host/cli/sandbox/lifecycle/tests.rs b/nvx-rs/src/host/cli/sandbox/lifecycle/tests.rs index 85b6ebbcd..63e6d108f 100644 --- a/nvx-rs/src/host/cli/sandbox/lifecycle/tests.rs +++ b/nvx-rs/src/host/cli/sandbox/lifecycle/tests.rs @@ -1,9 +1,8 @@ use std::cell::{Cell, RefCell}; -use super::stop::terminate_within; +use super::stop::{StopOutcome, terminate_within}; use super::*; use crate::host::cli::deadline::Deadline; -use crate::host::supervisor::StopOutcome; /// An OpenVMM of another runtime identity whose termination a test scripts, without a process. struct Scripted { diff --git a/nvx-rs/src/host/instance/mod.rs b/nvx-rs/src/host/instance/mod.rs index a9eece8c0..8e0865ff4 100644 --- a/nvx-rs/src/host/instance/mod.rs +++ b/nvx-rs/src/host/instance/mod.rs @@ -82,13 +82,7 @@ use self::locked::check_timeout; /// Returns a fresh launch capability, which is never all zero. pub fn generate_capability() -> Result<[u8; CAPABILITY_LEN]> { - let mut capability = [0u8; CAPABILITY_LEN]; - while capability == [0; CAPABILITY_LEN] { - getrandom::fill(&mut capability).map_err(|error| { - Error::other("cannot generate a control capability").with_source(error) - })?; - } - Ok(capability) + crate::host::vm::generate_capability() } /// Returns a control endpoint of an instance whose Linux control socket is `socket`: the socket diff --git a/nvx-rs/src/host/restore/tests/live_init.rs b/nvx-rs/src/host/restore/tests/live_init.rs index 0bf9c4361..91bfbe176 100644 --- a/nvx-rs/src/host/restore/tests/live_init.rs +++ b/nvx-rs/src/host/restore/tests/live_init.rs @@ -12,14 +12,13 @@ use crate::activation::{ ActivationRecord, Configuration, ExpectedResources, Export, ExportKind, Image, MachineIdMode, Network, OverlayMode, Publication, ReadyActivation, RestoreSection, TransactionId, Upper, View, }; -use crate::host::instance::generate_capability; use crate::host::nvxs::HostConnection; use crate::host::session; use crate::host::snapshot::SnapshotManifest; use crate::host::supervisor::{Launched, VmmCommand, spawn}; use crate::protocol::{ APP_ERROR, APP_EXEC, APP_EXIT, APP_MAPS, APP_READY, APP_STDERR, APP_STDOUT, APP_STOP, - APP_STOPPED, HostMap, Workload, WorkloadEnvironment, decode_app, encode_app, + APP_STOPPED, CAPABILITY_LEN, HostMap, Workload, WorkloadEnvironment, decode_app, encode_app, encode_exec_payload, encode_maps_payloads, }; @@ -295,7 +294,8 @@ fn run() -> Result<(), AnyError> { "--microvm-report".into(), report.as_os_str().to_owned(), ]; - let capability = generate_capability()?; + let mut capability = [0; CAPABILITY_LEN]; + getrandom::fill(&mut capability)?; let command = VmmCommand::new(vmm, options, &root); let mut owned = OwnedVm(Some(spawn(&command, &capability, log)?)); let running = owned.0.as_ref().ok_or("missing owned VM")?; diff --git a/nvx-rs/src/host/vm.rs b/nvx-rs/src/host/vm.rs index 013d250b4..dfc801f0e 100644 --- a/nvx-rs/src/host/vm.rs +++ b/nvx-rs/src/host/vm.rs @@ -86,6 +86,17 @@ impl fmt::Debug for Capability { } } +/// Returns a fresh launch capability, which is never all zero. +pub(crate) fn generate_capability() -> Result<[u8; CAPABILITY_LEN]> { + let mut capability = [0u8; CAPABILITY_LEN]; + while capability == [0; CAPABILITY_LEN] { + getrandom::fill(&mut capability).map_err(|error| { + Error::other("cannot generate a control capability").with_source(error) + })?; + } + Ok(capability) +} + /// Launch values that the caller saves and reloads to verify or reattach its VM. /// /// The library never serializes, stores or locates them. The endpoint is the name chosen at @@ -640,7 +651,7 @@ pub fn create( unused_output(Path::new(console))?; } } - let capability = Capability(crate::host::instance::generate_capability()?); + let capability = Capability(generate_capability()?); if let Some(snapshot) = from { unused_output(&view)?; if let Some(endpoint) = &ready_endpoint { diff --git a/nvx-rs/tests/managed_lifecycle.rs b/nvx-rs/tests/managed_lifecycle.rs index 3b7a895f8..4dc582fa3 100644 --- a/nvx-rs/tests/managed_lifecycle.rs +++ b/nvx-rs/tests/managed_lifecycle.rs @@ -36,7 +36,6 @@ use std::sync::mpsc; use std::thread; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; -use nvx::host::instance::generate_capability; use nvx::host::platform::process_start_time; use nvx::host::session::{self, ControlSession, ExecEvent}; use nvx::protocol::{ @@ -509,7 +508,8 @@ fn a_managed_guest_runs_request_scoped_workloads_until_it_stops() { let output_dir = PathBuf::from(variable("NVX_MANAGED_LIFECYCLE_OUTPUT_DIR")); fs::create_dir_all(&output_dir).unwrap(); let log = File::create(output_dir.join("managed-lifecycle.log")).unwrap(); - let capability = generate_capability().unwrap(); + let mut capability = [0; CAPABILITY_LEN]; + getrandom::fill(&mut capability).unwrap(); // OpenVMM reads its capability as soon as it starts, so its input already holds the // capability and its end, as `nvx::host::supervisor::spawn` prepares it. let (input, mut writer) = io::pipe().unwrap();