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
2 changes: 1 addition & 1 deletion doc/acceptance-case-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ The adapter's README said what each proved.
| `a_run_whose_clone_does_not_start_runs_nothing` | retired | Template selections and the template's `run` leave with the old clone path (#1121) |
| `a_run_whose_clone_s_vm_exits_during_its_execution_ends_with_the_clone_s_failure` | retired | The template's `run`, clone records and leases leave with the old clone path (#1121) |
| `a_selection_that_names_no_template_leases_nothing` | retired | Template selections and leases leave with the old clone path (#1121) |
| `a_start_refuses_an_export_whose_mapped_objects_changed_before_any_vmm_starts` | rewritten | `nvx-rs/src/host/filesystem/tests.rs::an_exported_object_that_a_workload_could_move_is_checked_again_before_a_start`; `nvx-rs/src/host/filesystem/tests.rs::swapped_objects_fail_verification` |
| `a_start_refuses_an_export_whose_mapped_objects_changed_before_any_vmm_starts` | rewritten | `nvx-rs/src/host/filesystem/tests.rs::an_exported_object_that_a_workload_could_move_is_checked_again_before_a_start`; `nvx-rs/src/host/filesystem/tests.rs::swapped_objects_fail_verification`; `nvx-rs/src/host/cli/sandbox/state/tests.rs::records_keep_the_export_with_its_pins_and_starts_refuse_changed_pins`, whose record keeps the pins beside the export, and whose start refuses a changed pin before the VM's configuration takes the export (SL6.5b) |
| `a_start_refuses_what_the_template_cannot_serve_before_any_vmm_starts` | rewritten | `nvx-rs/src/host/snapshot/tests.rs::snapshot_accepts_refuses_an_image_longer_than_its_placeholder`; `nvx-rs/src/host/snapshot/tests.rs::snapshot_accepts_refuses_each_scratch_geometry_mismatch`; `nvx-rs/src/host/snapshot/tests.rs::snapshot_accepts_refuses_each_memory_mismatch_without_rounding_or_overflow` |
| `a_template_without_a_nic_serves_clones_without_a_network` | rewritten | `nvx-rs/src/host/snapshot/tests.rs::snapshot_accepts_refuses_both_nic_presence_mismatches_and_each_named_address_mismatch` |
| `a_tombstone_leaves_the_running_clone_of_its_generation_to_its_lease` | retired | Tombstones, generations and leases leave with the old clone path (#1121) |
Expand Down
16 changes: 9 additions & 7 deletions doc/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -1300,13 +1300,15 @@ inside a writable part of a share. On Windows, it refuses a path that only a
verbatim `\\?\` path names, such as that of a directory whose name ends in a
period or a space, which the path without the prefix would not name. The
shares' children follow their resolved host paths. A managed sandbox stores
the plan in its typed configuration, in format 10. Each `start` checks it again,
as `run` does once it has verified the distro layer: it refuses a host path
that no longer holds a directory or regular file as recorded, a recorded path
whose file identity changed, and shares whose host paths came to overlap, such
as through a hard link or a bind mount. NVX no longer reads earlier
configuration formats, including those that recorded the shares as requested;
deprovision such a sandbox and provision it again.
the plan, with those identities beside it, in its typed configuration, in
format 10. Each `start` checks it again, as `run` does once it has verified the
distro layer: it refuses a host path that no longer holds a directory or
regular file as recorded, a recorded path whose file identity changed, and
shares whose host paths came to overlap, such as through a hard link or a bind
mount. NVX no longer reads earlier configuration formats, including those that
recorded the shares as requested, nor a configuration that earlier builds wrote
without those identities beside the plan; deprovision such a sandbox and
provision it again.

A share must not reach the sandbox's own files, which the workload could
otherwise read or change: `provision` refuses a share whose resolved host path
Expand Down
26 changes: 18 additions & 8 deletions nvx-rs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -937,16 +937,26 @@ action that needs more than 256 rules.

The `ExportPlan` of host paths through OpenVMM's aggregate virtio-fs device, for
a root's `ReservedTargets`, the blockless root's or the container root's: its
children, its ordered binds, which give the host-mapping table, and the
identities that each start checks again; `ExportPlan::check`, which checks a
recorded plan without reading the host, and the plan's OpenVMM options, boot
token, and mapping table; and `guest_path` (also at `nvx::`), the guest path at
which the host-path convention places a resolved host path.
children and its ordered binds, which give the host-mapping table, paths only;
`ExportPlan::check`, which checks a recorded plan without reading the host, and
the plan's mapping table, `ExportPlan::guest_table`; and `guest_path` (also at
`nvx::`), the guest path at which the host-path convention places a resolved
host path. A `VmConfig` lowers the plan into OpenVMM's options and its
`nvx_maps` boot token internally.

OpenVMM resolves the plan's paths only when it starts, so the caller keeps them
naming the objects that it means, as the module documents: across the launch
race, from its own checks of the host until OpenVMM opens the paths; through a
writable-part recheck before each later launch, since a workload that may write
a part of a child can move a mapped or denied object there and put a decoy at
its path; and for each durable denial, which holds only while its path names
the object that it hides.

The planner, which turns the mapped host paths at their guest targets, their
denied, allowed, and write paths, and the owner into a plan, and the checks of a
denied, allowed, and write paths, and the owner into a plan, with the pins of
the objects that a workload could move, and the checks of those pins and of the
plan's host objects before each start are the CLI's path rules, private to the
crate (feature `cli`).
crate (feature `cli`). The CLI records the pins beside the plan.

### `nvx::host::nvxs`

Expand Down Expand Up @@ -1930,7 +1940,7 @@ provision it again:

| File | Contents |
| --- | --- |
| `config.json` | The provisioned sandbox, in format 10 only: `image` holds its format, path and identity; `upper` is tagged as scratch with its path or RAM with `max_memory_percent`. `distro_binding` binds the image, and `scratch_binding` binds scratch or is `null` for RAM. The export is typed, `null` when absent. `network`, `null` without `--net`, holds `--net`, `--network-egress`, `--network-ingress`, `--host-loopback`, `--network-proxy`, each `null` when not given, and the `--host-loopback-forward` options, as given, with the egress rules in OpenVMM's form, from a policy file's compile if one was given. A sandbox that `snapshot restore` provisioned also records its snapshot's name under `source_snapshot`, the absolute path of the snapshot's store under `source_store`, and the program and arguments of the workload that each restore runs under `workload`; one that boots cold records none of them. An atomic pretty-JSON write uses mode 0600, and start reads the same strict typed fields without a legacy reader. |
| `config.json` | The provisioned sandbox, in format 10 only: `image` holds its format, path and identity; `upper` is tagged as scratch with its path or RAM with `max_memory_percent`. `distro_binding` binds the image, and `scratch_binding` binds scratch or is `null` for RAM. The export is typed, `null` when absent, and holds paths only; `export_pins`, `null` without an export, holds the identities of the export's denied and mapped objects that a workload could move, which each start checks before it launches the export, and a configuration without it is refused with the advice to deprovision the sandbox and provision it again. `network`, `null` without `--net`, holds `--net`, `--network-egress`, `--network-ingress`, `--host-loopback`, `--network-proxy`, each `null` when not given, and the `--host-loopback-forward` options, as given, with the egress rules in OpenVMM's form, from a policy file's compile if one was given. A sandbox that `snapshot restore` provisioned also records its snapshot's name under `source_snapshot`, the absolute path of the snapshot's store under `source_store`, and the program and arguments of the workload that each restore runs under `workload`; one that boots cold records none of them. An atomic pretty-JSON write uses mode 0600, and start reads the same strict typed fields without a legacy reader. |
| `sandbox.lock` | The store's lock, which serializes the lifecycle transitions |
| `exec.lock` | The exec lock, which an `exec` holds from before it finds the running VM until its workload ends, so that one `exec` at a time runs in the sandbox; it stays from the first `exec` until `deprovision` |
| `launch.json` | The store's journal of a start in progress, or of one that was interrupted |
Expand Down
7 changes: 0 additions & 7 deletions nvx-rs/public-api/linux.txt
Original file line number Diff line number Diff line change
Expand Up @@ -943,14 +943,12 @@ mod nvx::host::filesystem
struct nvx::host::filesystem::Bind
field nvx::host::filesystem::Bind::child
method nvx::host::filesystem::Bind::guest_source
field nvx::host::filesystem::Bind::identity
field nvx::host::filesystem::Bind::read_only
field nvx::host::filesystem::Bind::source
field nvx::host::filesystem::Bind::target
struct nvx::host::filesystem::ExportChild
field nvx::host::filesystem::ExportChild::allowed
field nvx::host::filesystem::ExportChild::denied
field nvx::host::filesystem::ExportChild::denied_identities
field nvx::host::filesystem::ExportChild::file
field nvx::host::filesystem::ExportChild::root
field nvx::host::filesystem::ExportChild::writable
Expand All @@ -960,12 +958,7 @@ field nvx::host::filesystem::ExportPlan::binds
method nvx::host::filesystem::ExportPlan::check
field nvx::host::filesystem::ExportPlan::children
method nvx::host::filesystem::ExportPlan::guest_table
method nvx::host::filesystem::ExportPlan::kernel_token
method nvx::host::filesystem::ExportPlan::openvmm_arguments
field nvx::host::filesystem::ExportPlan::owner
struct nvx::host::filesystem::FileIdentity
field nvx::host::filesystem::FileIdentity::device
field nvx::host::filesystem::FileIdentity::index
const nvx::host::filesystem::GUEST_EXPORT
const nvx::host::filesystem::MAX_CHILDREN
enum nvx::host::filesystem::Owner
Expand Down
7 changes: 0 additions & 7 deletions nvx-rs/public-api/windows.txt
Original file line number Diff line number Diff line change
Expand Up @@ -401,14 +401,12 @@ mod nvx::host::filesystem
struct nvx::host::filesystem::Bind
field nvx::host::filesystem::Bind::child
method nvx::host::filesystem::Bind::guest_source
field nvx::host::filesystem::Bind::identity
field nvx::host::filesystem::Bind::read_only
field nvx::host::filesystem::Bind::source
field nvx::host::filesystem::Bind::target
struct nvx::host::filesystem::ExportChild
field nvx::host::filesystem::ExportChild::allowed
field nvx::host::filesystem::ExportChild::denied
field nvx::host::filesystem::ExportChild::denied_identities
field nvx::host::filesystem::ExportChild::file
field nvx::host::filesystem::ExportChild::root
field nvx::host::filesystem::ExportChild::writable
Expand All @@ -418,12 +416,7 @@ field nvx::host::filesystem::ExportPlan::binds
method nvx::host::filesystem::ExportPlan::check
field nvx::host::filesystem::ExportPlan::children
method nvx::host::filesystem::ExportPlan::guest_table
method nvx::host::filesystem::ExportPlan::kernel_token
method nvx::host::filesystem::ExportPlan::openvmm_arguments
field nvx::host::filesystem::ExportPlan::owner
struct nvx::host::filesystem::FileIdentity
field nvx::host::filesystem::FileIdentity::device
field nvx::host::filesystem::FileIdentity::index
const nvx::host::filesystem::GUEST_EXPORT
const nvx::host::filesystem::MAX_CHILDREN
enum nvx::host::filesystem::Owner
Expand Down
4 changes: 3 additions & 1 deletion nvx-rs/src/host/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,7 @@ fn cold_config(options: &Options, rules: &NativeRules) -> Result<VmConfig> {
read_only: *access == MountAccess::ReadOnly,
})
.collect();
// A run launches its export right after it plans it, and never again, so it checks no pins.
let filesystem = filesystem::plan(&ExportRequest {
mappings: &mappings,
denied: PathList {
Expand All @@ -637,7 +638,8 @@ fn cold_config(options: &Options, rules: &NativeRules) -> Result<VmConfig> {
},
reserved: ReservedTargets::BLOCKLESS_ROOT,
})
.map_err(error_with_cause)?;
.map_err(error_with_cause)?
.map(|planned| planned.description);
cold_vm_config(options, rules, filesystem)
}

Expand Down
3 changes: 2 additions & 1 deletion nvx-rs/src/host/cli/run/guest_tokens/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ fn planned_boot_shares_keep_table_sources_targets_access_and_file_kind() {
reserved: ReservedTargets::BLOCKLESS_ROOT,
})
.unwrap()
.unwrap();
.unwrap()
.description;
let table = plan.guest_table();
assert_eq!(table[1].source, "0/single.txt");
let line = render("", Some(&plan)).unwrap();
Expand Down
15 changes: 8 additions & 7 deletions nvx-rs/src/host/cli/sandbox/lifecycle/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
//! Persistent sandboxes on the CLI's store and caller-owned [`crate::host::vm`] interface.
//!
//! Provision records the typed image and upper, image bindings, network and export. Start journals the
//! prepared capability and outputs before spawn, records the verified process, and commits the
//! matching saved VM only after authenticated readiness and mappings. Every lifecycle command
//! takes the CLI store's recoverable lock; indeterminate launches retain their evidence.
//! Provision records the typed image and upper, image bindings, network, and export with its pins.
//! Start journals the prepared capability and outputs before spawn, records the verified process,
//! and commits the matching saved VM only after authenticated readiness and mappings. Every
//! lifecycle command takes the CLI store's recoverable lock; indeterminate launches retain their
//! evidence.
//!
//! A restored sandbox, whose configuration names a named snapshot, restores it at each start in
//! place of a cold boot, under the snapshot's lock, through the blocking restore driver, which
Expand Down Expand Up @@ -38,7 +39,7 @@ use crate::host::cli::sandbox::{Artifacts, error_with_cause};
use crate::host::cli::session::Session;
use crate::host::cli::store::StateFile;
use crate::host::cli::{Exit, Result};
use crate::host::filesystem::ExportPlan;
use crate::host::filesystem::PlannedExport;
use crate::host::process::{Opened, Process};
use crate::host::runtime::{IdentityPart, LaunchOptions, Runtime, RuntimeIdentity};
use crate::host::sandbox::layer::SandboxLayer;
Expand Down Expand Up @@ -74,8 +75,8 @@ pub(crate) struct ProvisionConfig<'a> {
pub(crate) hostname: &'a str,
/// `--workload-user`.
pub(crate) workload_identity: (u32, u32),
/// The export plan of the shares, if any.
pub(crate) export: Option<&'a ExportPlan>,
/// The export plan of the shares, with its pins, if any.
pub(crate) export: Option<&'a PlannedExport>,
/// The network options, or `None` without `--net`.
pub(crate) network: Option<&'a NetworkRecord>,
}
Expand Down
11 changes: 6 additions & 5 deletions nvx-rs/src/host/cli/sandbox/lifecycle/start/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::fs;
use std::path::PathBuf;

use super::*;
use crate::host::filesystem::ExportPlan;
use crate::host::filesystem::PlannedExport;
use crate::host::paths::resolve;
use crate::host::sandbox::distro::DistroBinding;
use crate::host::sandbox::layer::{LayerRole, SandboxLayer};
Expand Down Expand Up @@ -111,11 +111,12 @@ fn starts_check_the_kernel_command_line_that_they_launch() {
let export = |shares: &[String]| plan_mounts(&sandbox_mounts(shares, &[], None).unwrap());
let read_only = export(std::slice::from_ref(&settings)).unwrap();
let writable = export(&[settings, output]).unwrap();
let tokens = |export: &PlannedExport| export_tokens(&export.description);
assert_eq!(
export_tokens(read_only.as_ref().unwrap()),
tokens(read_only.as_ref().unwrap()),
" virtfs_dir=/run/nvx/hostfs/root virtfs_tag=microvm virtfs_mode=ro virtfs_aggregate=1"
);
let configure = |cmdline: &str, scratch: Option<&Path>, export: Option<&ExportPlan>| {
let configure = |cmdline: &str, scratch: Option<&Path>, export: Option<&PlannedExport>| {
let config = ProvisionConfig {
hypervisor: "kvm",
memory_mib: 256,
Expand All @@ -130,7 +131,7 @@ fn starts_check_the_kernel_command_line_that_they_launch() {
let vm = config.vm_config().unwrap();
(config, vm)
};
let refusal = |cmdline: &str, scratch: Option<&Path>, export: Option<&ExportPlan>| {
let refusal = |cmdline: &str, scratch: Option<&Path>, export: Option<&PlannedExport>| {
let (config, vm) = configure(cmdline, scratch, export);
let error = check_command_line(&config, &vm).unwrap_err();
assert_eq!(error.code(), Some(ErrorCode::Unsupported), "{error:?}");
Expand All @@ -139,7 +140,7 @@ fn starts_check_the_kernel_command_line_that_they_launch() {
for upper in [None, Some(scratch.as_path())] {
for export in [None, read_only.as_ref(), writable.as_ref()] {
let (_, vm) = configure("", upper, export);
let fragment = export.map(export_tokens).unwrap_or_default();
let fragment = export.map(tokens).unwrap_or_default();
let room =
COMMAND_LINE_BUDGET - vm.kernel_command_line().unwrap().len() - fragment.len() - 2;
let (config, vm) = configure(&"x".repeat(room), upper, export);
Expand Down
2 changes: 0 additions & 2 deletions nvx-rs/src/host/cli/sandbox/lifecycle/start/tests/restore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,6 @@ fn restored_config(fixture: &Fixture, gpt: bool, ram: bool) -> VmConfig {
file: false,
writable: false,
denied: Vec::new(),
denied_identities: Vec::new(),
allowed: Vec::new(),
write: Vec::new(),
}],
Expand All @@ -277,7 +276,6 @@ fn restored_config(fixture: &Fixture, gpt: bool, ram: bool) -> VmConfig {
source: String::new(),
target: "/work".to_owned(),
read_only: true,
identity: None,
}],
owner: Owner::Vmm,
});
Expand Down
12 changes: 6 additions & 6 deletions nvx-rs/src/host/cli/sandbox/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use super::session::ExecRequest;
use super::{Exit, Result, io_error, output};
use crate::ErrorCode;
use crate::host::artifacts::{Guest, KERNEL_NAME, require_artifact};
use crate::host::filesystem::{ExportPlan, ReservedTargets};
use crate::host::filesystem::{ExportPlan, PlannedExport, ReservedTargets};
use crate::host::paths;
use crate::host::runtime::RuntimeIdentity;
use crate::host::sandbox::DEFAULT_ENTRYPOINT;
Expand Down Expand Up @@ -306,7 +306,7 @@ fn run_from(root: Option<&str>, options: Options, source: Option<&Source>) -> Re
validate_mounts(&mounts)?;
check_images(&layer, scratch.as_deref(), None)?;
// The shares are planned once, from their resolved host paths, and `provision` records
// the plan, whose identities each `start` checks again.
// the plan with its pins, which each `start` checks again.
let export = plan_mounts(&mounts)?;
if let Some(export) = &export {
export.check(ReservedTargets::CONTAINER_ROOT)?;
Expand Down Expand Up @@ -352,7 +352,7 @@ fn run_from(root: Option<&str>, options: Options, source: Option<&Source>) -> Re
Some(("state directory", Path::new(state_dir))),
layer.path(),
scratch.as_deref(),
export.as_ref(),
export.as_ref().map(|export| &export.description),
located.as_ref(),
)?;
lifecycle::provision(
Expand Down Expand Up @@ -426,11 +426,11 @@ fn run_from(root: Option<&str>, options: Options, source: Option<&Source>) -> Re
}

/// Returns the options of `options` that `provision` records beside the images of its launch, with
/// `export`, the export plan of its shares, and `network`, the record of its network options,
/// which a run records and reads back too.
/// `export`, the export plan of its shares with its pins, and `network`, the record of its network
/// options, which a run records and reads back too.
fn recorded<'a>(
options: &'a Options,
export: Option<&'a ExportPlan>,
export: Option<&'a PlannedExport>,
network: Option<&'a NetworkRecord>,
) -> ProvisionConfig<'a> {
ProvisionConfig {
Expand Down
Loading
Loading