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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion doc/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -1593,7 +1593,8 @@ which holds the NVX mechanics that the backend builds on, on the same runners
through the [`check-nvx-rs`](../.github/actions/check-nvx-rs/action.yml)
action. It runs rustfmt, Clippy with warnings denied for all targets and
features and for the library without default features, the unit, protocol
conformance-vector and property, and fake-OpenVMM instance tests, rustdoc with
conformance-vector and property, and fake-OpenVMM instance tests, the test that
keeps the references between the children of `nvx::host` acyclic, rustdoc with
warnings denied, the minimum Rust version check, and the macOS cross-check. It
also validates bundled artifact staging with a fixture package. Both actions set
`NVX_BUNDLE=skip` for `--all-features` builds, so that `bundled` stages nothing
Expand Down
16 changes: 11 additions & 5 deletions nvx-rs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@ nvx-rs/
│ │ the typed calls through which it runs the time ABI's steps and the reseed
│ └── bin/ nvx-rs, nvx-fake-openvmm, and the guest binaries
├── tests/ fake-OpenVMM, CLI, NVXS session, process-supervision, checked-handoff, store,
│ and guest-binary tests, the CLI's scenario tests and their harness, in cli/,
│ the store's record fixtures, the hand-written sandbox configurations of every
│ format, and the cases of the export and root boot tokens, in fixtures/, and
│ the conformance vectors of the wire formats, in vectors/
│ host-modules, and guest-binary tests, the CLI's scenario tests and their
│ harness, in cli/, the store's record fixtures, the hand-written sandbox
│ configurations of every format, and the cases of the export and root boot
│ tokens, in fixtures/, and the conformance vectors of the wire formats, in
│ vectors/
├── build.rs bundled-release staging
└── artifacts.json the NVX release that the `bundled` feature stages
```
Expand Down Expand Up @@ -1117,7 +1118,12 @@ The process-supervision test crashes orchestrator processes at each step of a
launch through the supervisor, reattaches to the VMM stand-ins that they leave
behind and stops them, and checks that a VMM inherits only its standard streams
and the descriptors or handles that it is allowed while another thread spawns
processes. The tests of `nvx::exec` drive a runner and a peer through a
processes. The host-modules test reads the crate's sources, and fails if the
production code of the children of `nvx::host`, outside comments and
`cfg(test)` code, references them in a cycle; it lists each cycle, with a
reference along each of its edges. So the children stack in layers, each above
the children that it references, which the test prints with `--nocapture`.
The tests of `nvx::exec` drive a runner and a peer through a
simulated workload, transport, and sink on a clock of their own, and, on Linux,
the tests of `nvx::guest::exec::binding` drive real programs through them, two
at once in one loop, one of them with a stalled sink; run as root, one of them
Expand Down
24 changes: 23 additions & 1 deletion nvx-rs/src/host/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ use serde_json::Value;

use crate::contract;
use crate::error::{Error, ErrorCode, Result};
use crate::host::paths;
use crate::host::platform;
use crate::host::sandbox::require_artifact;
use crate::host::store::{ArtifactIdentity, RuntimeIdentity};

/// Name of the release manifest at the root of a release archive or repository checkout.
Expand Down Expand Up @@ -873,6 +873,28 @@ fn required(path: PathBuf, description: &str) -> Result<PathBuf> {
Ok(path)
}

/// Checks that an artifact that a launch runs, such as the OpenVMM executable, is a regular
/// file, as [`require_existing`] does, and fails with [`ErrorCode::ArtifactMissing`].
pub(crate) fn require_artifact(path: &Path, description: &str) -> Result<()> {
require_existing(path, description, ErrorCode::ArtifactMissing)
}

/// Checks that `path` is a regular file, following links, as `require_file` of `nvx.py` does,
/// and fails with `code` and the message `{description} not found: {path}`, with `path` spelled
/// with its components alone, as [`Path::components`] reads them.
pub(crate) fn require_existing(path: &Path, description: &str, code: ErrorCode) -> Result<()> {
if path.is_file() {
return Ok(());
}
Err(Error::new(
code,
format!(
"{description} not found: {}",
paths::normalized(path).display()
),
))
}

/// The paths of the artifacts of an [`ArtifactDir`], which serialize as a JSON object such as:
///
/// ```json
Expand Down
4 changes: 2 additions & 2 deletions nvx-rs/src/host/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ use super::sandbox::{
use super::shell::format_command;
use super::{Exit, Result, check_interrupt, output};
use crate::ErrorCode;
use crate::host::artifacts::Guest;
use crate::host::artifacts::{Guest, require_artifact};
use crate::host::sandbox::mount::{
AGGREGATE_MOUNT_TARGET, MountKind, MountOwner, POLICY_KINDS, PolicyKind,
aggregate_child_argument, aggregate_child_name, aggregate_command_line_fragment,
aggregate_share_tokens, mount_argument, mount_kind, parse_mount_value,
require_mount_owner_supported,
};
use crate::host::sandbox::options::validate_run_share_targets;
use crate::host::sandbox::{SANDBOX_COMMAND_LINE_MAX_SIZE, has_kernel_separator, require_artifact};
use crate::host::sandbox::{SANDBOX_COMMAND_LINE_MAX_SIZE, has_kernel_separator};

/// The usage and help of the parser.
pub(super) const SYNTAX: Syntax = Syntax {
Expand Down
4 changes: 2 additions & 2 deletions nvx-rs/src/host/cli/sandbox/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use super::network::{NativeRules, NetworkOptions};
use super::shell::format_command;
use super::{Exit, Result, output};
use crate::ErrorCode;
use crate::host::artifacts::{Guest, KERNEL_NAME};
use crate::host::artifacts::{Guest, KERNEL_NAME, require_artifact};
use crate::host::network::{
Access, EgressRule, EgressRules, HostLoopback, LoopbackForward, NetworkPolicy, NetworkProxy,
};
Expand All @@ -48,7 +48,7 @@ use crate::host::sandbox::scratch::{CLAIM_RELEASE_WAIT, ScratchBinding};
use crate::host::sandbox::{
DEFAULT_ENTRYPOINT, DEFAULT_HOSTNAME, DEFAULT_WORKLOAD_IDENTITY, PIDS_MAX_LIMIT,
PreparedLaunch, SandboxLaunch, SandboxLayer, SandboxNetwork, SandboxPlan, SandboxSettings,
parse_workload_identity, require_artifact,
parse_workload_identity,
};
use crate::host::store::RuntimeIdentity;
use crate::host::supervisor::Inherited;
Expand Down
4 changes: 3 additions & 1 deletion nvx-rs/src/host/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
//! - [`supervisor`] launches a sandbox's VMM through the store's journal, passing it only the
//! descriptors or handles that the caller allows, stops it through the caller's graceful step,
//! with a bounded forced step on request, and reattaches to it after the orchestrator restarts
//! within the same host boot.
//! within the same host boot. The private `spawn` module, whose items `supervisor` re-exports,
//! starts the VMM detached, with no other descriptor or handle of the caller's.
//! - [`session`] speaks NVX's control protocol, `NVXC`, over [`nvxs`], which runs the host's role
//! of the NVXS session for it and for any other application protocol, over a platform
//! transport. [`platform`] holds the primitives of Linux and Windows hosts. Other hosts compile
Expand Down Expand Up @@ -61,6 +62,7 @@ pub mod report;
pub mod sandbox;
pub mod seal;
pub mod session;
mod spawn;
pub mod store;
pub mod supervisor;
#[cfg(unix)]
Expand Down
96 changes: 96 additions & 0 deletions nvx-rs/src/host/platform/inherit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//! The descriptors or handles of a caller that a VMM inherits.

#[cfg(not(any(unix, windows)))]
use std::marker::PhantomData;
#[cfg(unix)]
use std::os::fd::{AsFd, BorrowedFd};
#[cfg(windows)]
use std::os::windows::io::{AsHandle, BorrowedHandle};
#[cfg(target_os = "linux")]
use std::path::PathBuf;

/// The descriptor of the caller that an [`Inherited`] borrows.
#[cfg(unix)]
type Borrowed<'a> = BorrowedFd<'a>;
/// The handle of the caller that an [`Inherited`] borrows.
#[cfg(windows)]
type Borrowed<'a> = BorrowedHandle<'a>;
/// What an [`Inherited`] would borrow on a host without descriptors or handles, which has none.
#[cfg(not(any(unix, windows)))]
#[derive(Debug, Clone, Copy)]
struct Borrowed<'a>(std::convert::Infallible, PhantomData<&'a ()>);

/// A descriptor (Unix) or handle (Windows) of the caller, such as that of an open
/// [`File`](std::fs::File), that a VMM inherits under the number that it has in the caller, which
/// the caller can therefore name in the VMM's arguments.
///
/// [`spawn`](crate::host::supervisor::spawn) passes the VMM these and its standard streams, and
/// nothing else of the caller's:
///
/// - On Linux, the descriptor must be close-on-exec in the caller, as Rust opens its files, and
/// stays so: only the VMM's own process clears the flag, between its fork and its exec, so no
/// process that another thread spawns meanwhile inherits it. A spawn refuses a descriptor that
/// is not close-on-exec, since any process that the caller spawns would inherit it. The VMM
/// opens it again as `/proc/self/fd/N`, which `proc_path` returns.
/// - On Windows, the handle joins the explicit list of handles that the VMM inherits, and is
/// inheritable only while the VMM is created, after which the spawn restores its earlier
/// inheritability. Spawns that allow handles take that window one at a time, so several may
/// allow the same handle. A process that another thread creates in that window without such a
/// list, as `std::process::Command` does, can inherit it too.
///
/// Other hosts fail a spawn that passes any.
#[derive(Debug, Clone, Copy)]
pub struct Inherited<'a>(Borrowed<'a>);

impl<'a> Inherited<'a> {
/// Names the descriptor of `object`, such as a [`File`](std::fs::File).
#[cfg(unix)]
pub fn new(object: &'a impl AsFd) -> Self {
Self(object.as_fd())
}

/// Names the handle of `object`, such as a [`File`](std::fs::File).
#[cfg(windows)]
pub fn new(object: &'a impl AsHandle) -> Self {
Self(object.as_handle())
}
}

#[cfg(target_os = "linux")]
impl Inherited<'_> {
/// Returns the path through which the VMM opens the descriptor again: `/proc/self/fd/N`,
/// where `N` is its number in the caller and in the VMM.
pub fn proc_path(&self) -> PathBuf {
use std::os::fd::AsRawFd;

PathBuf::from(format!("/proc/self/fd/{}", self.0.as_raw_fd()))
}
}

#[cfg(unix)]
impl<'a> From<BorrowedFd<'a>> for Inherited<'a> {
fn from(descriptor: BorrowedFd<'a>) -> Self {
Self(descriptor)
}
}

#[cfg(windows)]
impl<'a> From<BorrowedHandle<'a>> for Inherited<'a> {
fn from(handle: BorrowedHandle<'a>) -> Self {
Self(handle)
}
}

#[cfg(unix)]
impl AsFd for Inherited<'_> {
fn as_fd(&self) -> BorrowedFd<'_> {
self.0
}
}

#[cfg(windows)]
impl AsHandle for Inherited<'_> {
fn as_handle(&self) -> BorrowedHandle<'_> {
self.0
}
}
2 changes: 1 addition & 1 deletion nvx-rs/src/host/platform/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::thread;
use std::time::{Duration, Instant};

use super::Transport;
use crate::host::supervisor::Inherited;
use super::inherit::Inherited;
use crate::host::sys::{self, Errno, PidFd};
use crate::hypervisor::Hypervisor;

Expand Down
1 change: 1 addition & 0 deletions nvx-rs/src/host/platform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::time::Duration;
use crate::error::{Error, Result};
use crate::hypervisor::Hypervisor;

pub(crate) mod inherit;
#[cfg(target_os = "linux")]
/// Linux implementations of host process, socket, and hypervisor primitives.
mod linux;
Expand Down
2 changes: 1 addition & 1 deletion nvx-rs/src/host/platform/other.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::process::Command;
use std::time::{Duration, Instant};

use super::Transport;
use crate::host::supervisor::Inherited;
use super::inherit::Inherited;
use crate::hypervisor::Hypervisor;

#[cfg(all(feature = "cli", unix))]
Expand Down
2 changes: 1 addition & 1 deletion nvx-rs/src/host/platform/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ use windows_sys::Win32::System::Threading::{
};

use super::Transport;
use crate::host::supervisor::Inherited;
use super::inherit::Inherited;
use crate::hypervisor::Hypervisor;

/// Whether this host can run NVX microVMs.
Expand Down
22 changes: 1 addition & 21 deletions nvx-rs/src/host/sandbox/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ pub use self::plan::{
};
pub use self::prepared::PreparedLaunch;
use crate::error::{Error, ErrorCode, Result};
use crate::host::artifacts::require_existing;
use crate::host::filesystem::ExportPlan;
use crate::host::paths;
use crate::net::is_valid_hostname;
use crate::protocol::cmdline;
use crate::protocol::root::RootImage;
Expand Down Expand Up @@ -154,26 +154,6 @@ pub fn require_file(path: &Path, description: &str) -> Result<()> {
require_existing(path, description, ErrorCode::Unavailable)
}

/// Checks that an artifact that a launch runs, such as the OpenVMM executable, is a regular
/// file, as [`require_file`] does, but fails with [`ErrorCode::ArtifactMissing`].
pub(crate) fn require_artifact(path: &Path, description: &str) -> Result<()> {
require_existing(path, description, ErrorCode::ArtifactMissing)
}

/// Checks that `path` is a regular file, as [`require_file`] does, and fails with `code`.
fn require_existing(path: &Path, description: &str, code: ErrorCode) -> Result<()> {
if path.is_file() {
return Ok(());
}
Err(Error::new(
code,
format!(
"{description} not found: {}",
paths::normalized(path).display()
),
))
}

/// Refuses a disk path that OpenVMM's `--microvm-sandbox-block` value could not hold: one with
/// a comma or semicolon. The message shows `path` as `shown`.
fn reject_disk_path(path: &Path, shown: &str) -> Result<()> {
Expand Down
1 change: 1 addition & 0 deletions nvx-rs/src/host/sandbox/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::path::PathBuf;
use super::*;
use crate::ErrorKind;
use crate::host::filesystem::{Bind, ExportChild, FileIdentity, Owner};
use crate::host::paths;
use crate::protocol::root::{RootLayout, RootUpper};

const DISTRO: &str = "distro,distro.erofs,11111111-1111-1111-1111-111111111111";
Expand Down
Loading
Loading