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
18 changes: 9 additions & 9 deletions doc/acceptance-case-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ its own in turn.

| Section | Tests | Kept | Rewritten | Retired |
| --- | ---: | ---: | ---: | ---: |
| [The adapter's scenarios](#the-adapters-scenarios) | 157 | 0 | 114 | 43 |
| [The adapter's scenarios](#the-adapters-scenarios) | 157 | 0 | 113 | 44 |
| [The adapter's other tests](#the-adapters-other-tests) | 169 | 0 | 9 | 160 |
| [MicroVM scenarios](#microvm-scenarios) | 54 | 50 | 3 | 1 |
| [CI](#ci) | 69 | 43 | 3 | 23 |
| [Docs](#docs) | 36 | 26 | 3 | 7 |
| [Docs](#docs) | 36 | 25 | 3 | 8 |

## The adapter's scenarios

Expand Down Expand Up @@ -129,9 +129,9 @@ The adapter's README said what each proved.
| `a_launch_refuses_shares_that_it_cannot_export` | rewritten | `nvx-rs/src/host/filesystem/tests.rs::policy_paths_hide_show_and_narrow_writes`; `nvx-rs/tests/security_checklist.rs::an_export_refuses_a_read_only_path_inside_a_writable_directory` |
| `a_launch_renders_each_element_of_its_network_through_the_plan` | rewritten | `nvx-rs/src/host/config/tests.rs::cold_boot_arguments_lower_layered_roots_networks_and_exports_by_path`; `nvx-rs/src/host/network/tests.rs::proxy_urls_name_the_gateway_as_the_guest_sees_it` |
| `a_launch_shares_host_paths_inside_the_authorized_directories` | rewritten | `nvx-rs/src/host/filesystem/tests.rs::policy_paths_hide_show_and_narrow_writes`; `nvx-rs/src/host/config/tests.rs::cold_boot_arguments_lower_layered_roots_networks_and_exports_by_path` |
| `a_layered_launch_hands_openvmm_the_checked_images` | rewritten | `nvx-rs/tests/checked_handoff.rs::a_prepared_launch_hands_the_vmm_its_checked_images_alone`; `nvx-rs/src/host/config/tests.rs::cold_boot_arguments_lower_layered_roots_networks_and_exports_by_path` |
| `a_layered_launch_hands_openvmm_the_checked_images` | rewritten | `nvx-rs/src/host/config/tests.rs::cold_boot_arguments_lower_layered_roots_networks_and_exports_by_path`. A launch names its images by path: the checked-file handoff leaves (SL6.3, #1122), as `images_replaced_after_the_checks_never_reach_the_vm` says |
| `a_layered_launch_passes_what_the_guest_checks_as_it_is` | rewritten | `nvx-rs/src/guest/init/boot/tests.rs::cold_boot_reads_the_actual_root_view_export_maps_and_cli_contracts` |
| `a_layered_launch_plans_its_blocks_and_boot_tokens_with_the_adapters_plan` | rewritten | `nvx-rs/src/host/config/tests.rs::cold_command_lines_keep_the_old_root_view_and_mapping_values`; `nvx-rs/src/host/config/tests.rs::cold_boot_arguments_lower_layered_roots_networks_and_exports_by_path` |
| `a_layered_launch_plans_its_blocks_and_boot_tokens_with_the_adapters_plan` | rewritten | `nvx-rs/src/host/config/tests.rs::cold_command_lines_carry_the_root_the_view_and_the_mapping_count`; `nvx-rs/src/host/config/tests.rs::cold_boot_arguments_lower_layered_roots_networks_and_exports_by_path` |
| `an_execution_finds_the_url_of_its_network_s_proxy_in_its_environment` | rewritten | `nvx-rs/src/guest/managed_agent/launch/tests.rs::restored_proxy_defaults_replace_template_values_before_exec_overrides`; `test-microvm --scenario sandbox-proxy-only` |
| `shares_read_their_host_path_their_target_and_their_access` | rewritten | `nvx-rs/src/host/cli/run/guest_tokens/tests.rs::planned_boot_shares_keep_table_sources_targets_access_and_file_kind` |
| `the_host_program_refuses_a_network_before_it_launches` | rewritten | `nvx-rs/tests/cli.rs::network_policies_that_the_planner_refuses_fail_before_launch` |
Expand All @@ -152,7 +152,7 @@ The adapter's README said what each proved.
| Scenario | Fate | Coverage, or why it retires |
| --- | --- | --- |
| `a_launch_that_the_hook_ends_after_its_checks_leaves_no_launch` | rewritten | A launch that fails after its checks leaves nothing of its own: `nvx-rs/src/host/vm/tests.rs::creation_rolls_back_its_log_and_never_removes_preexisting_outputs_or_inputs`; `nvx-rs/tests/security_checklist.rs::a_scratch_image_is_claimed_by_one_launch_and_checked_again_after_a_release`. The step hook retires |
| `images_replaced_after_the_checks_never_reach_the_vm` | rewritten | `nvx-rs/tests/checked_handoff.rs::a_vmm_finds_the_checked_images_past_replaced_links_from_another_directory` |
| `images_replaced_after_the_checks_never_reach_the_vm` | retired | The checked-file handoff leaves (SL6.3, #1122): a launch names its images by path, so what takes an image's path after its check is what OpenVMM opens. Callers stage their images in a directory that only their own account can write, as the docs of `host::sandbox::distro`, `host::sandbox::scratch` and `host::seal` require, so that no other account can replace a checked image |
| `the_hook_pauses_a_launch_at_each_of_its_steps` | retired | The adapter's step hook |

### `vm_layered` (43)
Expand Down Expand Up @@ -358,7 +358,7 @@ suite in its shards, and the public sandbox scenarios in its guest and sandbox j
| `virtio-features` | kept | |
| `virtio-net` | kept | |
| `vm-lifecycle` | kept | A [cold boot](#cold-boots) through the public API: `nvx-rs/tests/vm_lifecycle.rs::a_cold_vm_is_reattached_from_saved_values_and_stopped` |
| `workload-identity` | kept | |
| `workload-identity` | kept | Since SL6.3 (#1122), it also checks that a guest given OpenVMM's workload identity without `nvx_identity=`, or with another one, fails closed |

### Cold boots

Expand Down Expand Up @@ -500,9 +500,9 @@ this matrix. The README's own code is no doctest.
| `nvx-rs/src/guest/spawn.rs - guest::spawn::LaunchStage::from_code (line 201)` | rewritten | `nvx-rs/src/guest/spawn/tests.rs::launch_stages_keep_their_numbers`. The exec engine's launch failure and outcome, which the example read, leave with the engine (SL6.11a) |
| `nvx-rs/src/guest/terminal.rs - guest::terminal (line 10)` | kept | |
| `nvx-rs/src/guest/time/mod.rs - guest::time (line 9)` | kept | SL6.11c removed its capture and restore finish, which became crate-private: `guest::snapshot::capture` runs them |
| `nvx-rs/src/host/sandbox/prepared.rs - host::sandbox::prepared::PreparedLaunch (line 92)` | kept | Its launch type leaves with the old launch paths (#1087), whose PR retires it |
| `nvx-rs/src/host/sandbox/prepared.rs - host::sandbox::prepared::PreparedLaunch (line 92)` | retired | `PreparedLaunch` leaves with the checked-file handoff (SL6.3, #1122) |
| `nvx-rs/src/host/store/mod.rs - host::store (line 72)` | retired | The sandbox store leaves (SL6.1, #1121) |
| `nvx-rs/src/host/supervisor/mod.rs - host::supervisor (line 32)` | retired | The store-coupled supervisor leaves with the sandbox store (SL6.1, #1121); `host::supervisor` stays only as the transitional public path of the low-level spawn, until SL6.3 (#1122), with no doc test |
| `nvx-rs/src/host/supervisor/mod.rs - host::supervisor (line 32)` | retired | The store-coupled supervisor leaves with the sandbox store (SL6.1, #1121), and `host::supervisor`, the transitional public path of the low-level spawn, with SL6.3 (#1122): `host::vm` spawns VMMs, and the tests of the spawn moved to `nvx-rs/src/host/spawn/tests.rs` and `nvx-rs/tests/process.rs` |
| `nvx-rs/src/host/template/clone.rs - host::template::clone::ClonePlan (line 247)` | retired | `ClonePlan` and `CloneRequest` leave with the old clone path (SL6.2b, #1121) |
| `nvx-rs/src/host/template/plan.rs - host::template::plan::TemplatePlan (line 137)` | retired | `TemplatePlan` leaves with the template store (SL6.2a, #1121) |
| `nvx-rs/src/host/template/store.rs - host::template::store::Store (line 473)` | retired | The template store leaves (SL6.2a, #1121) |
Expand All @@ -512,4 +512,4 @@ this matrix. The README's own code is no doctest.
| `nvx-rs/src/host/snapshot/capture.rs - host::snapshot::capture::finish_capture (line 48)` | kept | |
| `nvx-rs/src/host/store/mod.rs - host::store (line 104)` | retired | The sandbox store leaves (SL6.1, #1121) |
| `nvx-rs/README.md`, its Example: an instance through `LaunchSpec` and the sandbox store | rewritten | Nothing compiles it. `nvx-rs/examples/host-workflows.rs` compiles the documented host workflows, and the library's guide (SL6.10a) replaces the example. SL6.1 (#1121) made it the cold boot that `nvx-rs/src/lib.rs - (line 57)` compiles |
| `nvx-rs/examples/external-launch.rs` | kept | Compiled with the crate's targets, and run by hand on a host |
| `nvx-rs/examples/external-launch.rs` | kept | Compiled with the crate's targets, and run by hand on a host. Since SL6.3 (#1122), which deletes `host::supervisor`, a minimal launcher of its own on `std::process` starts its VMM |
18 changes: 11 additions & 7 deletions doc/design/2.1-machine-model-and-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,17 @@ lifetime is one of:
| Host attachments: console endpoints, the network endpoint, block files, and exported host directories | Host, matching the recorded identities | VM process |
| Online processor and RAM activation targets | Host, within snapshot limits | Restore |

The host fixes the workload policy of a fresh boot. An optional nonzero
numeric UID and GID become the fixed workload identity, and an optional
one-shot or managed lifecycle selects whether the guest agents run one
workload or stay resident for sequential requests over the control console.
Both are command-line tokens that the machine profile owns and that caller
arguments cannot supply. A managed lifecycle requires a fixed workload
identity and a live, authenticated control console.
The host fixes the workload policy of a fresh boot. NVX's launchers name a
fixed workload identity, a nonzero numeric UID and GID, and a one-shot or
managed lifecycle, which selects whether the guest agents run one workload or
stay resident for sequential requests over the control console, with
command-line tokens of their own (see
[Effective Command Line](5.1-boot-protocol.md#512-effective-command-line)). A
managed lifecycle requires a fixed workload identity and a live, authenticated
control console. The machine profile also takes an identity and a lifecycle as
tokens of its own, which caller arguments cannot supply. NVX's guests select
neither from them, and fail closed when the profile names an identity that the
launcher's token does not.

## 2.1.3 Storage Configuration

Expand Down
20 changes: 6 additions & 14 deletions doc/design/4.2-guest-agents-and-workload-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,10 @@ obligations; the one exception is the time daemon, which
[guest-agent guidelines](../guest-agent-guidelines.md) give the init's
contract, with nvx-init as the worked example.

A layered root's guest runs one of two inits. nvx-init, NVX's PID 1 for
layered roots, runs the new launches, and the restores of platform captures,
in one VM-wide space, as Figure 4.2 shows; it also serves the Fresh boot that
a platform capture takes, which has no root yet. A layered launch that still
carries OpenVMM's older lifecycle token, `nvx_lifecycle=`, which NVX's CLI no
longer adds, runs the older init agent, whose sandbox agent gives each
execution private namespaces and a cgroup in the
[layered sandbox root](4.1-guest-filesystem-and-image-model.md#411-layered-sandbox-root).
The agent of the
A layered root's guest runs nvx-init, NVX's PID 1 for layered roots, which
runs its launches, and the restores of platform captures, in one VM-wide
space, as Figure 4.2 shows; it also serves the Fresh boot that a platform
capture takes, which has no root yet. The agent of the
[blockless managed root](4.1-guest-filesystem-and-image-model.md#413-blockless-managed-root)
is PID 1 in the guest's initramfs root, and runs its executions with
nvx-init's plain-process core.
Expand Down Expand Up @@ -55,9 +50,7 @@ Launching an execution belongs to the init, not to the library's capture or
activation. nvx-init's executions, and those of the blockless managed root,
are plain processes of their agent: each child takes its credentials and
enters its working directory itself, in its agent's space, without a root of
its own or a placement barrier. The older layered sandbox root keeps its
execution entry, which enters private namespaces and the workload root, and
its placement barrier.
its own or a placement barrier.
[Run the executions](../guest-agent-guidelines.md#run-the-executions) gives
nvx-init's steps.

Expand All @@ -68,5 +61,4 @@ resource boundary. An execution's process group, which its end,
cancellation, timeout and reset kill, is a cleanup scope, not a namespace or
a cgroup: a process that leaves the group escapes it. The init owns
termination, capture admission and cleanup; no workspace or per-execution
enforcement of `nvx` supplies them. The older layered sandbox root keeps its
namespace and cgroup cleanup.
enforcement of `nvx` supplies them.
22 changes: 12 additions & 10 deletions doc/design/5.1-boot-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,18 @@ Launchers add runtime tokens of their own: the
[boot tokens of a layered root](4.1-guest-filesystem-and-image-model.md#411-layered-sandbox-root),
one share token per host-share child, the number of host mappings for a
blockless managed root, and a request to create the workload account. A
launcher that passes OpenVMM neither the workload identity nor the lifecycle
may name them for a blockless guest with tokens of its own,
`nvx_identity=UID:GID` and `nvx_mode=one-shot` or `nvx_mode=managed`, which
the guest's `/init` reads as it reads the host-owned tokens. In the one-shot
mode, `/init` runs the workload that `nvx_entrypoint=` and its `nvx_arg=`
tokens name once, as that identity, after the network and the shares, and
powers the VM off with the workload's status. Each `nvx_arg=` token carries
one argument, which cannot contain a space, since `/init` splits the command
line on spaces. A caller may also declare the RAM-backed overlay layout, whose
overlay upper the guest agents keep on a tmpfs, with exactly one
launcher names the workload identity and the lifecycle of a blockless guest
with tokens of its own, `nvx_identity=UID:GID` and `nvx_mode=one-shot` or
`nvx_mode=managed`, which the guest's `/init` reads. It ignores the lifecycle
token that OpenVMM appends, and fails closed on OpenVMM's identity tokens: a
boot that has them stops with `NVX-WORKLOAD-ERROR` and status 125 before it
starts anything, unless `nvx_identity=` names the same UID and GID. In the
one-shot mode, `/init` runs the workload that `nvx_entrypoint=` and its
`nvx_arg=` tokens name once, as that identity, after the network and the
shares, and powers the VM off with the workload's status. Each `nvx_arg=`
token carries one argument, which cannot contain a space, since `/init` splits
the command line on spaces. A caller may also declare the RAM-backed overlay
layout, whose overlay upper the guest agents keep on a tmpfs, with exactly one
`nvx_overlay_upper=ramfs` token; any other form of that token is rejected.

Embedded NULs are rejected, and the complete NUL-terminated command line must
Expand Down
7 changes: 4 additions & 3 deletions doc/project-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,8 @@ verification of the pinned OpenVMM release.
initramfs builds, plus the Azure Linux variants of the container-entry
scripts. The ten compiled `/sbin/nvx-*` programs live under `nvx-rs/src/guest`
and `nvx-rs/src/bin`. `init` controls early boot and then starts an
initramfs program named by `nvx_exec=`, the Alpine-only `nvx-init-agent`
sandbox profile, `nvx-managed-agent` for a managed workload, a one-shot
initramfs program named by `nvx_exec=`, `nvx-init` for a layered root,
`nvx-managed-agent` for a managed workload, a one-shot
workload named by `nvx_entrypoint=`, a virtio restore probe for benchmark
profiling, or the interactive shell. On an interactive boot, `init` prints a
stable distribution boot marker for Alpine and Azure Linux, and
Expand All @@ -242,7 +242,8 @@ the container launch and entry scripts that only the Alpine initramfs installs;
the entry script stages Alpine's musl loader and `setpriv` inside the
workload's root.

The sandbox scripts resolve fixed virtio-blk roles through sysfs, mount the
The older sandbox scripts, `nvx-init-agent` and the container scripts, which
`init` no longer starts, resolve fixed virtio-blk roles through sysfs, mount the
EROFS `distro` layer as the overlay's only lower layer, under the upper that
the host declares, an ext4 scratch disk or a tmpfs, place the workload in its
cgroup before release, construct its mount/PID/UTS namespaces, enter its
Expand Down
39 changes: 17 additions & 22 deletions guest/common/init
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,6 @@ case "$cmdline" in
*" nvx_sandbox=1 "*) sandbox_mode=true ;;
*) sandbox_mode=false ;;
esac
new_init=false
if [ "$sandbox_mode" = true ]; then
case "$cmdline" in
*" nvx_lifecycle="*) ;;
*) new_init=true ;;
esac
fi

fatal() {
echo "$*" >&2
Expand Down Expand Up @@ -160,17 +153,17 @@ nvx_exec=
nvx_exec_seen=false
workload_uid=
workload_gid=
openvmm_uid=
openvmm_gid=
workload_lifecycle=one-shot
control_tty=
workload_account=
entrypoint=
entrypoints=0
# Kernel command-line tokens are deliberately split on spaces. OpenVMM's
# options give the lifecycle and the workload identity tokens of their own; a
# blockless launch of the CLI gives the CLI's instead, nvx_mode=one-shot or
# nvx_mode=managed and nvx_identity=UID:GID, and names its one-shot workload
# with nvx_entrypoint= and nvx_arg= tokens, whose values become the positional
# parameters, in order.
# Kernel command-line tokens are deliberately split on spaces. A blockless
# launch of the CLI gives nvx_mode=one-shot or nvx_mode=managed and
# nvx_identity=UID:GID, and names its one-shot workload with nvx_entrypoint=
# and nvx_arg= tokens, whose values become the positional parameters, in order.
# shellcheck disable=SC2086
set -f
set --
Expand All @@ -182,10 +175,10 @@ for token in $cmdline; do
nvx_exec=${token#*=}
[ -n "$nvx_exec" ] || fatal "nvx_exec must not be empty"
;;
nvx_workload_uid=*) workload_uid=${token#*=} ;;
nvx_workload_gid=*) workload_gid=${token#*=} ;;
nvx_identity=*) read_workload_identity "${token#*=}" ;;
nvx_lifecycle=* | nvx_mode=*) workload_lifecycle=${token#*=} ;;
nvx_workload_uid=*) openvmm_uid=${token#*=} ;;
nvx_workload_gid=*) openvmm_gid=${token#*=} ;;
nvx_mode=*) workload_lifecycle=${token#*=} ;;
nvx_entrypoint=*)
entrypoint=${token#*=}
entrypoints=$((entrypoints + 1))
Expand All @@ -196,7 +189,14 @@ for token in $cmdline; do
esac
done
set +f
if [ -n "$nvx_exec" ] && [ "$new_init" = false ]; then
# OpenVMM's --microvm-workload-identity appends nvx_workload_uid= and
# nvx_workload_gid=, which no longer name the workload's identity. A boot that
# has them fails closed unless nvx_identity= names the same one.
if [ -n "$openvmm_uid$openvmm_gid" ] &&
[ "$openvmm_uid:$openvmm_gid" != "$workload_uid:$workload_gid" ]; then
workload_fatal "nvx_identity must name OpenVMM's workload identity"
fi
if [ -n "$nvx_exec" ] && [ "$sandbox_mode" = false ]; then
case "$nvx_exec" in
/*) ;;
*) fatal "nvx_exec must be an absolute initramfs path" ;;
Expand Down Expand Up @@ -317,11 +317,6 @@ if [ -n "$nip" ]; then
fi
fi
if [ "$sandbox_mode" = true ]; then
if [ "$new_init" = false ]; then
[ -x /sbin/nvx-init-agent ] ||
workload_fatal "selected guest cannot act as sandbox control"
exec /sbin/nvx-init-agent
fi
[ -x /sbin/nvx-init ] ||
workload_fatal "selected guest cannot act as sandbox control"
# nvx-time boot returns before its child publishes the daemon PID.
Expand Down
Loading
Loading