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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions doc/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -1593,13 +1593,14 @@ 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
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
instead of downloading the pinned release, and the `aci_edge_sandboxes` action
also sets `ACI_EDGE_SANDBOXES_AGENT_BUNDLE=skip`, so that `agent-bundled` does
not download an edge agent bundle.
conformance-vector and property, and fake-OpenVMM instance tests, a test that
the direct children of `nvx::host` depend on one another without cycles, 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 instead of downloading the pinned release, and the `aci_edge_sandboxes`
action also sets `ACI_EDGE_SANDBOXES_AGENT_BUNDLE=skip`, so that `agent-bundled`
does not download an edge agent bundle.

On Linux, the `nvx-rs` job also lints and tests the
[guest binaries](../nvx-rs/README.md#guest-binaries) for
Expand Down
17 changes: 11 additions & 6 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-module layering, 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,8 +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
simulated workload, transport, and sink on a clock of their own, and, on Linux,
processes. The host-module test reads the sources of `src/host/` and fails if
the direct children of `nvx::host` depend on one another in a cycle, naming one
reference for each dependency in it: break the cycle by moving the item that a
lower module takes from a higher one down, and re-exporting it at its old path.
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
also settles a real cgroup.
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_file`](crate::host::sandbox::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`](crate::host::sandbox::require_file)
/// does, and fails with `code`.
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
7 changes: 7 additions & 0 deletions nvx-rs/src/host/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@
//! The private `paths` module makes host paths absolute and resolves their links, with
//! `std::path` and `std::fs`, for the sandbox launch profile and the CLI.
//!
//! The children of `host` depend on one another without cycles: each builds only on children
//! beneath it, as the [`supervisor`] does on the [`store`], and the store on the private `spawn`
//! module, which starts a VMM and whose items the supervisor re-exports. An item that a lower
//! child needs from a higher one moves down, and the higher child re-exports it where it was.
//! `tests/host_modules.rs` reads the sources of `host` and fails on a cycle.
//!
//! The host modules deny unsafe code. On Unix, the private `sys` module confines the system calls
//! that the standard library lacks behind documented safe functions, which call `nix`. The
//! Windows platform module and the Windows module of [`seal`] still call `windows-sys` directly,
Expand All @@ -61,6 +67,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
97 changes: 97 additions & 0 deletions nvx-rs/src/host/platform/inherit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//! The descriptors (Unix) or handles (Windows) of a caller that a VMM inherits when
//! [`supervisor::spawn`](crate::host::supervisor::spawn) starts it.

#[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