diff --git a/doc/run.md b/doc/run.md index 59958ed0d..caec8b03c 100644 --- a/doc/run.md +++ b/doc/run.md @@ -1125,6 +1125,28 @@ that they cannot look up, as through a link loop, rather than take it for no manifest. Keep provisioned layers and their manifests read-only, and never rewrite them in place. +`provision` also binds the scratch image: its identity, kind, and length, +which the VM's writes leave unchanged. `start` checks the binding from +metadata alone, refuses a scratch image that was replaced or resized since +`provision`, and claims the image for the sandbox until its OpenVMM exits: while +it runs, a second sandbox, or a `run`, on the same image fails with +`another sandbox claims the ext4 scratch image PATH`. On Linux, the identity is +the device and inode number, which a file that is created once the image is +removed can take again: such a replacement, of the same length, is not refused. +`run` and `start` hand OpenVMM the files that their checks opened, rather than +paths that something could replace meanwhile. On Linux, OpenVMM inherits their +descriptors, and its command line names them `/proc/self/fd/N`, as the command +that `run` prints shows; on Windows, its command line names them by their final +paths, `\\?\` paths that go through no link, and the checked files keep the +images from being replaced, renamed, or deleted until the guest is ready, as +long as `run` or `start` runs until then: if it ends before OpenVMM has opened +them, as when it crashes, a replacement at their paths would reach OpenVMM +([#780](https://github.com/microsoft/nvx/issues/780)). +There, a final path with a comma or semicolon, or ending in `.vhd`, `.vhdx`, +`.vmgs`, or `.iso`, which OpenVMM could not attach as a raw image, fails the +launch. Images are attached raw: stage an image in a container format, such as +a VHD, as a raw file first. + Lifecycle transitions fail closed: `start` rejects a sandbox that is already running; `exec` and `stop` require a live OpenVMM process; and `deprovision` refuses to remove a running sandbox or unknown files. The state directory is diff --git a/nvx-rs/Cargo.toml b/nvx-rs/Cargo.toml index e035b9181..02e5cfbef 100644 --- a/nvx-rs/Cargo.toml +++ b/nvx-rs/Cargo.toml @@ -99,12 +99,14 @@ sha2 = { version = "0.10", optional = true } serde_json = "1.0.120" tempfile = "3.10" -# The job objects, process security descriptors, and handles of the process supervision tests. +# The job objects, process security descriptors, handles, and process lists of the process +# supervision and CLI tests. [target.'cfg(windows)'.dev-dependencies] windows-sys = { version = "0.61", features = [ "Win32_Foundation", "Win32_Security", "Win32_Security_Authorization", + "Win32_System_Diagnostics_ToolHelp", "Win32_System_JobObjects", "Win32_System_Threading", ] } diff --git a/nvx-rs/README.md b/nvx-rs/README.md index 66cc1decc..14e8dbbdf 100644 --- a/nvx-rs/README.md +++ b/nvx-rs/README.md @@ -39,8 +39,8 @@ nvx-rs/ │ ├── host/ feature `host` (default): artifacts, launch, network and egress policy files, │ │ filesystem, NVXS connection, session, process, platform, VM instances, │ │ sandbox stores, outcome reports, file seals, and the sandbox launch profile -│ │ of `nvx.py` and its distro bindings, with the Python semantics they keep; -│ │ with feature `cli`, the `nvx-rs` CLI +│ │ of `nvx.py`, its distro and scratch bindings, and its prepared launches, +│ │ with the Python semantics they keep; with feature `cli`, the `nvx-rs` CLI │ ├── testing/ feature `testing`: the fake OpenVMM │ ├── guest/ feature `guest`: the programs that run in Linux guests, the guest's NVXS │ │ connection, the libraries through which a guest init or agent opens the @@ -50,9 +50,9 @@ nvx-rs/ │ │ root, and reads a GPT disk's partition table through its block device, and │ │ 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, store, and guest-binary -│ tests, the store's record fixtures, and the golden fixtures that Python -│ recorded for the ports of its code, in fixtures/ +├── tests/ fake-OpenVMM, CLI, NVXS session, process-supervision, checked-handoff, store, +│ and guest-binary tests, the store's record fixtures, and the golden fixtures +│ that Python recorded for the ports of its code, in fixtures/ ├── build.rs bundled-release staging └── artifacts.json the NVX release that the `bundled` feature stages ``` @@ -81,7 +81,7 @@ nvx-rs/ | `nvx::host::supervisor` | The supervisor of a sandbox's VMM, beneath the CLI, `Instance`, and external orchestrators, which keep the configuration, the authorization, and the application protocol. `launch` runs the store's launch steps under the sandbox's lock: it writes the journal's marker, spawns OpenVMM with the claimed log, records its `ProcessRef` right after, and returns a `Starting` launch that the caller commits once the VMM serves its control endpoint, or abandons. A launch that fails never leaves an unrecorded VMM running. `VmmCommand::inherit` lists the descriptors (Linux) or handles (Windows) that the VMM inherits under their numbers, besides its standard streams, and it inherits nothing else of the caller's: on Linux, they must be close-on-exec, a flag that only the VMM's own process clears; on Windows, they join the VMM's explicit handle list. `Vmm::stop` runs one graceful step of the caller's and waits for the VMM's exit event until a deadline, and takes a bounded forced step through the VMM's verified identity only on request. `reattach` recovers an interrupted launch, and holds the recorded VMM again after the orchestrator restarts within the same host boot: `Live`, `Mismatched` with another runtime identity, `Exited`, or `Indeterminate` | | `nvx::host::seal` | `FileSeal`: what the host reports about an open file from its metadata alone, without reading it: the platform scheme, the device or volume serial number, the file ID, the kind, the length, the modification time, and on Linux the change time, with a versioned serde form. A seal detects accidental change, not tampering; see [Distro bindings](#distro-bindings) | | `nvx::host::session`, `platform`, `report` | The authenticated control session, which carries NVX's control protocol, `NVXC`, one frame per DATA record of a `HostConnection`; Linux and Windows primitives; and OpenVMM's outcome report | -| `nvx::host::sandbox` | The launch profile of `nvx.py sandbox`, ported from the one that `scripts/nvx_tools/sandbox.py` held: `SandboxLaunch`, with the EROFS distro `SandboxLayer`, the one layer that a sandbox takes, the ext4 scratch disk, the entrypoint, arguments, hostname, workload identity, and resource limits, and the `SandboxPlan` that it makes of them. `plan`: `SandboxPlan`, the typed plan of a cold layered launch, which is persistable in a versioned serde form: one read-only `distro` block device, an EROFS filesystem or a GPT disk with the identity that the guest expects of it, if any, an upper, the ext4 scratch disk or RAM, the launch settings, and optionally a `SandboxNetwork`, the guest network and its typed `NetworkPolicy`, whose proxy's URL as the guest sees it the plan reports. A plan knows only the `distro` and `scratch` `BlockRole`s, and gives, for every combination, its `--microvm-sandbox-block` and `--microvm-workload-identity` arguments, with no scratch block device for a RAM upper, and those of its network, which `nvx::host::network` lowers, and its kernel command line, with the boot tokens of `nvx::protocol::root`, within the 1024-byte budget that OpenVMM leaves it, refusing one whose root tokens the guest's parser refuses. `mount`: `SandboxMount` live shares of host directories and files, their guest targets and the reserved ones, their access policies and owner, the aggregate of several shares, and the checks of their host paths. `options`: the checks that `nvx.py sandbox` and `nvx.py run` apply to their options: the attribution of `--mount-deny`, `--mount-allow`, and `--mount-write` to shares, the systemd entrypoint check and the explicit verification of the distro layer, the targets of `nvx.py run` shares, and the managed exec environment; see [Python parity](#python-parity). `distro`: the `DistroBinding` of a distro layer, which explicit verification makes or a staging caller imports, and its check from metadata alone, which returns the layer open; see [Distro bindings](#distro-bindings) | +| `nvx::host::sandbox` | The launch profile of `nvx.py sandbox`, ported from the one that `scripts/nvx_tools/sandbox.py` held: `SandboxLaunch`, with the EROFS distro `SandboxLayer`, the one layer that a sandbox takes, the ext4 scratch disk, the entrypoint, arguments, hostname, workload identity, and resource limits, and the `SandboxPlan` that it makes of them. `plan`: `SandboxPlan`, the typed plan of a cold layered launch, which is persistable in a versioned serde form: one read-only `distro` block device, an EROFS filesystem or a GPT disk with the identity that the guest expects of it, if any, an upper, the ext4 scratch disk or RAM, the launch settings, and optionally a `SandboxNetwork`, the guest network and its typed `NetworkPolicy`, whose proxy's URL as the guest sees it the plan reports. A plan knows only the `distro` and `scratch` `BlockRole`s, and gives, for every combination, its `--microvm-sandbox-block` and `--microvm-workload-identity` arguments, with no scratch block device for a RAM upper, and those of its network, which `nvx::host::network` lowers, and its kernel command line, with the boot tokens of `nvx::protocol::root`, within the 1024-byte budget that OpenVMM leaves it, refusing one whose root tokens the guest's parser refuses. `mount`: `SandboxMount` live shares of host directories and files, their guest targets and the reserved ones, their access policies and owner, the aggregate of several shares, and the checks of their host paths. `options`: the checks that `nvx.py sandbox` and `nvx.py run` apply to their options: the attribution of `--mount-deny`, `--mount-allow`, and `--mount-write` to shares, the systemd entrypoint check and the explicit verification of the distro layer, the targets of `nvx.py run` shares, and the managed exec environment; see [Python parity](#python-parity). `distro`: the `DistroBinding` of a distro layer, which explicit verification makes or a staging caller imports, and its check from metadata alone, which returns the layer open; see [Distro bindings](#distro-bindings). `scratch`: the `ScratchBinding` of a scratch image, its identity, kind, and length, and its check from metadata alone, which claims the image for one sandbox at a time and returns it open. `prepared`: `PreparedLaunch`, the launch of a plan with the checked files of its block devices, which renders the plan's OpenVMM arguments and kernel command line, naming the checked files as `/proc/self/fd/N` on Linux and by their final paths on Windows, and lists the descriptors that the VMM inherits through `VmmCommand::inherit`; see [Checked handoff](#checked-handoff) | | `nvx::testing::fake_openvmm` | A fake OpenVMM that serves the control console and runs scripted workloads without a VM, for direct launches and for the managed and one-shot sandboxes of `nvx-rs sandbox` and `nvx.py sandbox` | | `nvx::host::cli` | Feature `cli`: the `nvx-rs` executable; see [The nvx-rs CLI](#the-nvx-rs-cli) | | `nvx::guest` | The programs that run in Linux guests; see [Guest binaries](#guest-binaries) | @@ -207,7 +207,8 @@ path, as it records every path, with the binding of the layer there, under another name, `start` checks the manifest beside the link's target, so `provision` verifies the layer there as well, and it records only a binding that the check accepts there. `start` checks the binding before it launches -OpenVMM, and holds the checked layer until the start ends. `run` and `start` +OpenVMM, and hands OpenVMM the checked layer; see +[Checked handoff](#checked-handoff). `run` and `start` plan the launch with the image format and the identity that the binding records, which name an EROFS layer, and launch it over a scratch disk, the one layered root that the guest's shell init assembles. A configuration @@ -217,6 +218,93 @@ after its distro binding was made: its length differs`; both add `deprovision the sandbox and provision it again`, since a new provision verifies the layer again. +## Checked handoff + +A launch hands the VMM exactly the objects that its checks found, never a path +that something could replace between the check and the VMM's open of it. + +The scratch image, which the VMM writes, has a binding of its own, a +`ScratchBinding`: its platform scheme, its device or volume and file ID, its +kind, a regular file, and its length, which fixes the guest's block device, in +a versioned serde form without times. `ScratchBinding::bind` makes it without +reading the image, and `ScratchBinding::check` opens the image, compares it with +the binding from its metadata alone, and claims it for one sandbox at a time, +until the sandbox's VMM exits, then compares it again, since the launch whose +claim it waited for may have changed it. On Linux, the file ID is the inode +number, which a file that is created once the image is removed can take again, +so a replacement made that way, with the image's length, passes the check. A +check that finds the image claimed waits until its deadline, such as +`CLAIM_RELEASE_WAIT` from now, and then fails with `ErrorKind::TimedOut`. On +Linux, the claim is an exclusive `flock` lock of the checked descriptor's open +file description, which the VMM inherits and holds; a process that another +thread spawns holds a copy of the descriptor from its fork until it executes +its program, which the wait covers. On Windows, the check's handle is a guard +that holds no access to the image's content, shares reading and writing, and +denies deleting and renaming the image, and the check makes sure that no other +process has the image open for writing, as another sandbox's VMM does, then +opens the image for writing, sharing every access, as the claim's handle, +which the VMM inherits: so the VMM holds the claim from its start, even if the +launcher ends before the VMM has opened the image. Claims exclude the launches +of NVX, not a process that writes the image on its own. + +`PreparedLaunch::new` takes a `SandboxPlan`, the `CheckedDistro` that +`DistroBinding::check` returned for its image, and, for an upper on scratch, +the `CheckedScratch` that `ScratchBinding::check` returned, and refuses files +that are not those of the plan. It renders the plan's OpenVMM arguments and +kernel command line, holds the checked files, and lists the descriptors that +the VMM inherits: + +- On Linux, the VMM inherits the checked descriptors through + `VmmCommand::inherit`, `PreparedLaunch::inherited`, and the arguments name + each `/proc/self/fd/N`, `distro:file:/proc/self/fd/N,ro` and + `scratch:file:/proc/self/fd/M`, which OpenVMM opens: the link reaches the + checked file whatever later takes its path. Every other descriptor stays + close-on-exec, as the supervisor guarantees. A checked descriptor that took a + standard stream's number, as when the caller had closed that stream, first + moves above the standard streams, with its open file description and its + claim. +- On Windows, the arguments name each checked file by its final path, the + absolute `\\?\` path that its handle gives, which resolves every link that + the plan's path went through, and does not depend on the VMM's directory. + The checked files hold that path in place: the distro layer's file denies + writing and deleting the layer, the scratch image's guard denies deleting and + renaming it, and NTFS renames no directory while a file below it is open; + OpenVMM's opens share them. A launcher that ends before OpenVMM has opened + the images, as when it crashes, closes the checked files early, so that a + replacement at their paths would then reach OpenVMM unchecked, and + [#780](https://github.com/microsoft/nvx/issues/780) tracks holding the paths + in that case; `start` never commits such a launch, and the sandbox's next + transition ends its VMM. The VMM inherits one handle, which no argument + names: the scratch image's claim, through `VmmCommand::inherit` and + `PreparedLaunch::inherited`. A final path that OpenVMM could not take as the + path of a raw image, with a comma or semicolon, or ending in `.vhd`, `.vhdx`, + `.vmgs`, or `.iso`, which a link, the directory of a relative path, or the + case of a name on disk can bring, fails the preparation with + `ErrorKind::Unsupported`. + +Images are raw: OpenVMM attaches a file's bytes as they are, and chooses a +container format by the file's extension, which `/proc/self/fd/N` lacks, so a +caller whose images use a container format, such as a fixed VHD, stages a raw +file. The paths in the arguments hold for the launch alone: the plan, which +callers persist, keeps the real paths. The caller adds its own arguments, +starts the VMM, and drops the prepared launch once the guest reports +authenticated Ready, or once it has torn down a launch that failed; the VMM's +own opens then keep the images, and what it inherited keeps the scratch +image's claim. Staging, the checks, and the preparation of a launch read no +content of the images. + +`nvx-rs sandbox provision` records the scratch image's binding under +`scratch_binding` in `config.json`, at the image's resolved path, and `start` +checks it after the distro binding, and launches OpenVMM through a prepared +launch, which it drops once the guest is ready or OpenVMM has exited. A +scratch image that changed fails as a distro layer does, such as with +`IMAGE changed after its scratch binding was made: its length differs; +deprovision the sandbox and provision it again`, and one that another sandbox +claims with `another sandbox claims the ext4 scratch image IMAGE`. `run` binds +and claims the scratch image itself, and runs OpenVMM through a prepared +launch until it exits; its command, which it prints, names the checked files, +except in a dry run, which launches nothing. + ## Python parity `nvx::host::network::policy`, `nvx::host::sandbox`, and the guest lookups of @@ -319,7 +407,9 @@ extracted release, with them in `guest/` and `bin/`. instead of the root's, such as `nvx-fake-openvmm` in tests. A relative path is relative to the working directory. A managed `start` refuses it, since the root's install metadata identifies the root's own OpenVMM alone; see - [Runtime identity](#runtime-identity). + [Runtime identity](#runtime-identity). On Windows, it cannot name a batch + file, which Windows would run through `cmd.exe`, reading OpenVMM's arguments + as shell syntax: `run` refuses it. ### `artifacts` @@ -388,8 +478,11 @@ before them. - `run` prints `>> COMMAND`, OpenVMM's command line, quoted as `subprocess.list2cmdline` quotes it on Windows and as `shlex.join` does elsewhere. Unless `--dry-run` is given, it then runs OpenVMM with the - standard streams and exits with its status. `--outcome-report` asks OpenVMM - for its outcome report. + standard streams and exits with its status; the command then names the + checked distro layer and scratch image as `/proc/self/fd/N` on Linux, which + OpenVMM inherits, and by their final paths on Windows, as + [Checked handoff](#checked-handoff) describes. + `--outcome-report` asks OpenVMM for its outcome report. - `provision` records the sandbox in its state directory. `start` launches OpenVMM detached with the managed lifecycle, passes it a fresh capability through its standard input, and waits until the guest answers. `exec` runs a @@ -402,15 +495,15 @@ before them. The state directory is the directory of the sandbox's [store](src/host/store/mod.rs), the runtime records that every host of a sandbox shares, and `config.json` is the CLI's own record beside them. It also -records the [distro binding](#distro-bindings) of the sandbox, and its network -as the typed `SandboxNetwork`, in the serde form of the plan's network, in -place of the network options. `nvx-rs` refuses to start a sandbox that -`nvx.py` or an earlier `nvx-rs` provisioned, which has no binding or no typed -network: +records the [distro binding](#distro-bindings) and the +[scratch binding](#checked-handoff) of the sandbox, and its network as the +typed `SandboxNetwork`, in the serde form of the plan's network, in place of +the network options. `nvx-rs` refuses to start a sandbox that `nvx.py` or an +earlier `nvx-rs` provisioned, which has no bindings or no typed network: | File | Contents | | --- | --- | -| `config.json` | The provisioned sandbox, in the lowest of formats 1 to 6 that holds it, with the binding of its distro layer under `distro_binding` and its network under `network`, `null` for none, written as `json.dumps(value, indent=2, sort_keys=True)` writes it, with mode 0600, through a temporary file | +| `config.json` | The provisioned sandbox, in the lowest of formats 1 to 6 that holds it, with the binding of its distro layer under `distro_binding`, that of its scratch image under `scratch_binding`, and its network under `network`, `null` for none, written as `json.dumps(value, indent=2, sort_keys=True)` writes it, with mode 0600, through a temporary file | | `sandbox.lock` | The store's lock, which serializes the lifecycle transitions | | `launch.json` | The store's journal of a start in progress, or of one that was interrupted | | `runtime.json` | The store's runtime record of the running VM, in its format 4: the OpenVMM process, by its ID, its start time, and the host's boot, its control endpoint, `STATE/control.sock` or `//./pipe/openvmm-microvm-HEX` on Windows, and the [runtime identity](#runtime-identity) of its start | @@ -476,8 +569,10 @@ cancels a workload, and Ctrl-C ends the session, nor workload accounts that lacks a required feature, or that predates the request, is stopped as a failed start is, and the start fails with `the guest image lacks control features that layered sandboxes need (...)`, -which names the missing features. `nvx.py` does not ask; this and the check of -the [distro binding](#distro-bindings) are the intended changes of behavior. The +which names the missing features. `nvx.py` does not ask; this, the checks of +the [distro binding](#distro-bindings) and of the +[scratch binding](#checked-handoff), and the claim of the scratch image are the +intended changes of behavior. The guest agents of NVX advertise bits 0, 2, and 4 in sandbox mode. #### Other differences from nvx.py @@ -518,6 +613,14 @@ guest agents of NVX advertise bits 0, 2, and 4 in sandbox mode. way out. The command spells out OpenVMM's defaults and the rules in their canonical form, with the network options after the plan's other options; see [Sandbox networking](../doc/run.md#sandbox-networking). +- `run` and `start` hand OpenVMM the files that their checks opened: its + command line names them `/proc/self/fd/N` on Linux, and by their final + paths, `\\?\` paths, on Windows, and on every host, a sandbox's scratch image + is claimed while it runs, so a second sandbox, or a `run`, on the same image + fails; see [Checked handoff](#checked-handoff). OpenVMM inherits no other + descriptor or handle of `run` beyond its standard streams, as `subprocess.run` + passes none with `close_fds`: on Windows, OpenVMM's handle list holds copies + of `run`'s standard handles and the scratch image's claim alone. ### Test hooks @@ -528,9 +631,13 @@ one-shot runs, which run `/bin/echo`, `/bin/sleep`, `/bin/true`, or `/bin/false`, write an outcome report if asked, and exit with the workload's status. Kernel command-line tokens make it fail: `fake_exit_on_start=STATUS`, `fake_exit_status=STATUS`, `fake_guest_features=MASK`, and -`fake_legacy_guest=1`, among others; see `nvx::testing::fake_openvmm`. A -managed `start` also reads the root's [runtime identity](#runtime-identity), so -a test root holds build provenance, as the roots of the golden scenarios do. +`fake_legacy_guest=1`, among others; see `nvx::testing::fake_openvmm`. Like +OpenVMM, it opens the sandbox's block devices at the paths that its options +name and holds them until it exits, after `fake_block_delay_ms=MS` if asked, +and records what each held in `fake-openvmm-TOKEN-blocks.json` next to the +kernel. A managed `start` also reads the root's +[runtime identity](#runtime-identity), so a test root holds build provenance, +as the roots of the golden scenarios do. ```bash cd nvx-rs @@ -550,7 +657,11 @@ one-shot runs, the lifecycles of sandboxes in every configuration format, and the refusals of the lifecycle operations. Each step records the exit status, standard output and error, and the state files, with the root, process IDs, and random identifiers as placeholders, and the devices, file IDs, and times of the -distro binding, which `config.json` has recorded since the recording, as well. +distro and scratch bindings, which `config.json` has recorded since the +recording, and on Linux the descriptor numbers of `/proc/self/fd/N`, with +which launches have named their checked files since then, as well; on +Windows, launches have named them by their final paths, `\\?\` and the path, +since then. Their kernel command lines name the image format of the distro layer, which the launch profile has rendered since the recording too, and their network options render through the typed policy, which `config.json` records under diff --git a/nvx-rs/src/host/cli/sandbox/lifecycle.rs b/nvx-rs/src/host/cli/sandbox/lifecycle.rs index 3527a87d9..77c02d0ba 100644 --- a/nvx-rs/src/host/cli/sandbox/lifecycle.rs +++ b/nvx-rs/src/host/cli/sandbox/lifecycle.rs @@ -7,8 +7,8 @@ //! //! ```text //! / -//! config.json the launch that provision records, in formats 1 to 6, with the binding -//! of its distro layer and its network +//! config.json the launch that provision records, in formats 1 to 6, with the bindings +//! of its distro layer and of its scratch image, and its network //! sandbox.lock the store's lock, which serializes the lifecycle transitions //! launch.json the store's journal of a start in progress, or of an interrupted one //! runtime.json the store's runtime record of the running VM, in its format 4 @@ -25,10 +25,10 @@ //! [`provision`], [`start`], [`stop`], and [`deprovision`] hold the [`LifecycleLock`] for their //! whole transition, and [`exec_workload`] only while it finds the running VM, so a long workload //! cannot delay a stop. `config.json` also records the binding of the distro layer that -//! `provision` verified, which [`start`] checks from metadata alone, and the network as its typed -//! policy, which [`start`] checks again, in place of the network options that -//! `sandbox_lifecycle.py` recorded: it refuses to start a sandbox that `nvx.py` or an earlier -//! `nvx-rs` provisioned, which has no binding or no typed network. +//! `provision` verified, and that of the scratch image, which [`start`] checks from metadata +//! alone, and the network as its typed policy, which [`start`] checks again, in place of the +//! network options that `sandbox_lifecycle.py` recorded: it refuses to start a sandbox that +//! `nvx.py` or an earlier `nvx-rs` provisioned, which has no bindings or no typed network. //! //! Every transition but provision finds the sandbox's VM with [`supervisor::reattach`], as an //! orchestrator does after it restarts: it ends a start that was interrupted, holds a recorded @@ -39,6 +39,11 @@ //! [`supervisor::launch`], under the runtime identity that the install metadata of the artifacts //! names, and [`stop`] asks the guest to shut down, waits for OpenVMM to exit, and reports a VM //! that does not, without ending it otherwise. +//! +//! [`start`] hands OpenVMM, through a [`PreparedLaunch`] of the checked files, the objects that +//! the checks found, rather than paths that something could replace meanwhile, and claims the +//! scratch image for this sandbox while OpenVMM runs. It holds the checked files until the guest +//! is ready, or until it has ended an OpenVMM whose start failed. use std::ffi::OsString; use std::fs::{File, OpenOptions}; @@ -50,9 +55,9 @@ use super::lock::{ os_error_class, }; use super::state::{ - CONFIG_FORMATS, RecordedDistro, config_int, config_text, deserialize_binding, - deserialize_launch, deserialize_network, hex, member, random_bytes, read_json, - serialize_launch, temporary_path, write_json, + CONFIG_FORMATS, RecordedDistro, RecordedScratch, config_int, config_text, deserialize_binding, + deserialize_launch, deserialize_network, deserialize_scratch_binding, hex, member, + random_bytes, read_json, serialize_launch, temporary_path, write_json, }; use super::{Artifacts, error_with_cause}; use crate::ErrorKind; @@ -65,7 +70,8 @@ use crate::host::python::fs::{self, os_error_message}; use crate::host::python::json::{Document, Node}; use crate::host::python::path::PurePath; use crate::host::sandbox::distro::DISTRO_POLICY_REVISION; -use crate::host::sandbox::{SandboxLaunch, require_file}; +use crate::host::sandbox::scratch::CLAIM_RELEASE_WAIT; +use crate::host::sandbox::{PreparedLaunch, SandboxLaunch, require_file}; use crate::host::store::{ self, IdentityPart, LOCK_NAME, LockedStore, OUTCOME_NAME, RUNTIME_NAME, Recovery, SandboxStore, }; @@ -319,7 +325,8 @@ pub(crate) fn write_exec_outcome(path: &str, result: &ExecResult) -> Result { } /// Records a launch and its distro layer, at the path at which it was verified, with the layer's -/// binding, in a new state directory, as `provision` does. +/// binding, in a new state directory, as `provision` does, with the binding of its scratch image, +/// at the path that it records for it. pub(crate) fn provision( state_path: &str, launch: &SandboxLaunch, @@ -335,7 +342,11 @@ pub(crate) fn provision( return Err(Exit::error("sandbox is already provisioned")); } launch.validated()?; - write_json(&config_path, &serialize_launch(launch, distro, config)?) + let scratch = RecordedScratch::new(launch)?; + write_json( + &config_path, + &serialize_launch(launch, distro, &scratch, config)?, + ) } /// What a transition finds of the sandbox's VM, through [`supervisor::reattach`]. @@ -465,15 +476,22 @@ fn start_locked( let outcome_path = state_dir.join(OUTCOME_NAME); unlink_missing_ok(&outcome_path)?; let launch = deserialize_launch(&config)?; - // The check reads no content of the layer or its manifest, and never verifies them again. - // Its file holds the layer until the start ends: on Windows, it keeps anything from modifying - // or replacing the layer before OpenVMM, whose read-only open shares it, has opened the layer. + // The checks read no content of the distro layer, its manifest, or the scratch image, and + // never verify them again. The prepared launch holds the files that they return, and hands + // OpenVMM those objects: on Linux, OpenVMM inherits their descriptors; on Windows, it opens + // them by their final paths, which the files keep anything from replacing before OpenVMM, + // whose opens share them, has opened them. The scratch image's check also claims it for this + // sandbox, until OpenVMM exits. let binding = deserialize_binding(&config)?; - let _distro = binding + let scratch_binding = deserialize_scratch_binding(&config)?; + let distro = binding .check(launch.distro(), DISTRO_POLICY_REVISION) - .map_err(distro_error)?; + .map_err(binding_error)?; // Reading the network checks its policy again, as provision did, before anything launches. let launch = launch.with_network(deserialize_network(&config)?); + let scratch = scratch_binding + .check(launch.scratch(), Instant::now() + CLAIM_RELEASE_WAIT) + .map_err(binding_error)?; // The plan takes the format and identity of the distro layer from its binding. let plan = launch.bound_plan(&binding)?; let [executable, kernel, initrd] = artifacts.require()?; @@ -484,9 +502,17 @@ fn start_locked( "the system returned an all-zero control capability", )); } + let hypervisor = config_text(&config, "hypervisor")?; + let memory_mib = config_int(&config, "memory_mib")?; + let prepared = PreparedLaunch::new( + plan, + distro, + Some(scratch), + &config_text(&config, "cmdline")?, + )?; // The control endpoint, which the launch chooses, goes between these arguments. let mut head = vec![executable]; - head.extend(plan.openvmm_arguments()?); + head.extend(prepared.openvmm_arguments().iter().cloned()); head.extend( [ "--microvm-lifecycle", @@ -496,12 +522,12 @@ fn start_locked( ] .map(str::to_owned), ); - head.push(config_text(&config, "hypervisor")?); + head.push(hypervisor); head.push("--memory".to_owned()); - head.push(format!("{}M", config_int(&config, "memory_mib")?)); + head.push(format!("{memory_mib}M")); head.extend(["--kernel".to_owned(), kernel, "--initrd".to_owned(), initrd]); head.push("--cmdline".to_owned()); - head.push(plan.kernel_command_line(&config_text(&config, "cmdline")?)?); + head.push(prepared.kernel_command_line().to_owned()); head.extend(["--virtio-console", "none", "--microvm-control-console"].map(str::to_owned)); let tail = vec![ "--microvm-control-auth-stdin".to_owned(), @@ -515,11 +541,17 @@ fn start_locked( command.push(format!("listen={endpoint}")); command.extend(tail); let arguments = command[1..].iter().map(OsString::from).collect(); - Ok(VmmCommand::new(&command[0], arguments, directory)) + let mut vmm = VmmCommand::new(&command[0], arguments, directory); + // OpenVMM inherits the checked files that the prepared launch passes, and nothing else. + vmm.inherit = prepared.inherited(); + Ok(vmm) }) .map_err(error_with_cause)?; let pid = starting.process().reference().pid; let Err(error) = check_ready(starting.endpoint(), pid, &capability, timeout) else { + // The guest is ready, so OpenVMM holds the images: the checked files can go, which + // releases their guards, while OpenVMM keeps the scratch image's claim. + drop(prepared); return starting.commit().map(drop).map_err(error_with_cause); }; // OpenVMM closes the control endpoint as it tears the VM down, before it publishes its @@ -537,6 +569,8 @@ fn start_locked( Ok(_) => None, Err(failure) => Some(failure.message().to_owned()), }; + // The checked files can go too, which releases their guards and the scratch image's claim. + drop(prepared); let error = match status { Some(status) if !matches!(error, Exit::Interrupted) => { Exit::error(startup_exit_message(&outcome_path, status)?) @@ -553,11 +587,13 @@ fn start_locked( Err(error) } -/// Returns the error of a start whose distro layer fails the check of its binding: a binding that -/// no longer holds needs a new provision, which verifies the layer again. -fn distro_error(error: crate::Error) -> Exit { +/// Returns the error of a start whose distro layer or scratch image fails the check of its +/// binding: a binding that no longer holds needs a new provision, which verifies the layer and +/// binds the image again, while a scratch image that another sandbox claims, and a failure of the +/// host, do not. +fn binding_error(error: crate::Error) -> Exit { match error.kind() { - ErrorKind::Io => error.into(), + ErrorKind::Io | ErrorKind::TimedOut => error.into(), _ => Exit::error(format!( "{}; deprovision the sandbox and provision it again", error.message() diff --git a/nvx-rs/src/host/cli/sandbox/mod.rs b/nvx-rs/src/host/cli/sandbox/mod.rs index 8552da0d6..86c15bc04 100644 --- a/nvx-rs/src/host/cli/sandbox/mod.rs +++ b/nvx-rs/src/host/cli/sandbox/mod.rs @@ -2,7 +2,11 @@ //! `scripts/nvx.py`, with its options, defaults, checks, outputs, and exit statuses. //! //! - `run`, the default, boots a one-shot sandbox: it prints the OpenVMM command as `>> COMMAND`, -//! then runs it on the terminal, unless `--dry-run`, and exits with OpenVMM's status. +//! then runs it on the terminal, unless `--dry-run`, and exits with OpenVMM's status. It runs +//! OpenVMM through a [`PreparedLaunch`] of the distro layer that it verified and of the scratch +//! image, which it claims, so the command names their checked files: on Linux as +//! `/proc/self/fd/N`, and on Windows by their final paths. A dry run, which launches nothing, +//! names their paths. //! - `provision`, `start`, `exec`, `stop`, and `deprovision` manage a persistent sandbox in its //! `--state-dir`; see [`lifecycle`]. //! @@ -14,9 +18,10 @@ mod lifecycle; mod lock; mod state; +#[cfg(not(windows))] use std::process::{Child, Command}; use std::thread; -use std::time::Duration; +use std::time::{Duration, Instant}; use super::args::{Parser, Receiver, Spec, Syntax, invalid_choice, invalid_value}; use super::artifacts::{Located, OPENVMM_VARIABLE, locate}; @@ -31,17 +36,21 @@ use crate::host::python::builtins::{Int, parse_float}; use crate::host::python::fs::os_error_message; use crate::host::python::path::PurePath; use crate::host::python::shell::format_command; +use crate::host::sandbox::distro::{DISTRO_POLICY_REVISION, DistroBinding}; use crate::host::sandbox::mount::{MountOwner, PolicyKind}; use crate::host::sandbox::options::{ PolicyPathOption, SandboxOperation, SandboxOptions, sandbox_mounts, validate_sandbox_systemd_policy, }; use crate::host::sandbox::plan::guest_gateway; +use crate::host::sandbox::scratch::{CLAIM_RELEASE_WAIT, ScratchBinding}; use crate::host::sandbox::{ - DEFAULT_ENTRYPOINT, DEFAULT_HOSTNAME, DEFAULT_WORKLOAD_IDENTITY, SandboxLaunch, SandboxLayer, - SandboxNetwork, SandboxSettings, parse_workload_identity, require_file, + DEFAULT_ENTRYPOINT, DEFAULT_HOSTNAME, DEFAULT_WORKLOAD_IDENTITY, PreparedLaunch, SandboxLaunch, + SandboxLayer, SandboxNetwork, SandboxPlan, SandboxSettings, parse_workload_identity, + require_file, }; use crate::host::store::RuntimeIdentity; +use crate::host::supervisor::Inherited; pub(crate) use self::lifecycle::ProvisionConfig; use self::state::RecordedDistro; @@ -768,8 +777,24 @@ pub(super) fn run(root: Option<&str>, options: Options) -> Result { let [openvmm, kernel, initrd] = artifacts.require()?; // The plan takes the format and identity of the distro layer from its binding. let plan = launch.bound_plan(&binding)?; + // A dry run launches nothing, so it shows the plan's own paths; a run launches through + // the checked files, which it holds until OpenVMM exits. + let (arguments, command_line, prepared) = if options.dry_run { + ( + plan.openvmm_arguments()?, + plan.kernel_command_line(&options.cmdline)?, + None, + ) + } else { + let prepared = prepare_run(&launch, &binding, plan, &options.cmdline)?; + ( + prepared.openvmm_arguments().to_vec(), + prepared.kernel_command_line().to_owned(), + Some(prepared), + ) + }; let mut command = vec![openvmm]; - command.extend(plan.openvmm_arguments()?); + command.extend(arguments); command.extend( [ "--microvm-lifecycle", @@ -788,17 +813,17 @@ pub(super) fn run(root: Option<&str>, options: Options) -> Result { "--initrd".to_owned(), initrd, "--cmdline".to_owned(), - plan.kernel_command_line(&options.cmdline)?, + command_line, ]); if let Some(report) = &options.outcome_report { command.extend(["--microvm-report".to_owned(), path_text(report)]); } output::print_stdout(&format!(">> {}", format_command(&command))) .map_err(Exit::from_os_error)?; - if options.dry_run { + let Some(prepared) = prepared else { return Ok(()); - } - Err(Exit::Status(run_openvmm(&command)?)) + }; + Err(Exit::Status(run_openvmm(&command, &prepared.inherited())?)) } (SandboxOperation::Run | SandboxOperation::Provision, None) => { unreachable!("run and provision build their launch") @@ -806,23 +831,45 @@ pub(super) fn run(root: Option<&str>, options: Options) -> Result { } } +/// Checks the distro layer of a one-shot run against `binding`, which the run's verification of +/// the layer made, binds and claims its scratch image, and prepares the launch of `plan` with the +/// checked files and the user's kernel command line `cmdline`. +/// +/// The checks read no content, so the layer that the run verified is the one that OpenVMM gets: +/// one that changed meanwhile fails its check. +fn prepare_run( + launch: &SandboxLaunch, + binding: &DistroBinding, + plan: SandboxPlan, + cmdline: &str, +) -> Result { + let distro = binding.check(launch.distro(), DISTRO_POLICY_REVISION)?; + let scratch = ScratchBinding::bind(launch.scratch())? + .check(launch.scratch(), Instant::now() + CLAIM_RELEASE_WAIT)?; + PreparedLaunch::new(plan, distro, Some(scratch), cmdline).map_err(Exit::from) +} + /// Runs OpenVMM on the terminal, as `subprocess.run(command)` does, and returns its status. /// +/// OpenVMM inherits the descriptors or handles `inherit`, under their numbers in this process, and +/// no other of this process beyond its standard streams, as with `subprocess.run`, which closes +/// the others on Linux and passes none on Windows: on Linux, `inherit` names the checked files of +/// the run; on Windows, it names the claim's handle of the run's scratch image, OpenVMM's handle +/// list holds copies of this process's standard handles and that handle alone, and OpenVMM opens +/// the checked files by their final paths, which the checked files hold in place. +/// /// OpenVMM shares the terminal and its process group, so Ctrl-C reaches it too. As /// `subprocess.run` does, the CLI then gives it 0.25 s to exit on Unix before it kills it, and on /// Windows, where Python cannot interrupt its wait, waits for it to exit; either way the command /// ends as interrupted, also when OpenVMM exited first, since Python's wait raises /// `KeyboardInterrupt` once Ctrl-C arrives. -fn run_openvmm(command: &[String]) -> Result { - let mut child = Command::new(&command[0]) - .args(&command[1..]) - .spawn() - .map_err(|error| Exit::error(spawn_error(&error, &command[0])))?; +fn run_openvmm(command: &[String], inherit: &[Inherited<'_>]) -> Result { + let mut openvmm = OneShot::spawn(command, inherit)?; loop { - let status = poll(&mut child)?; + let status = openvmm.poll()?; if platform::interrupted() { if status.is_none() { - end_interrupted(&mut child)?; + end_interrupted(&mut openvmm)?; } return Err(Exit::Interrupted); } @@ -833,14 +880,78 @@ fn run_openvmm(command: &[String]) -> Result { } } +/// The OpenVMM process of a one-shot run. +struct OneShot { + /// The process, which `std::process` started. + #[cfg(not(windows))] + child: Child, + /// The process, which [`platform::spawn_foreground`] started. + #[cfg(windows)] + process: platform::LaunchedProcess, +} + +impl OneShot { + /// Starts OpenVMM with `command`, with the descriptors or handles `inherit`, as + /// [`run_openvmm`] says. + fn spawn(command: &[String], inherit: &[Inherited<'_>]) -> Result { + let failed = |error: std::io::Error| Exit::error(spawn_error(&error, &command[0])); + #[cfg(windows)] + { + let arguments: Vec = command[1..].iter().map(Into::into).collect(); + platform::spawn_foreground(std::path::Path::new(&command[0]), &arguments, inherit) + .map(|process| Self { process }) + .map_err(failed) + } + #[cfg(not(windows))] + { + let mut child = Command::new(&command[0]); + child.args(&command[1..]); + platform::inherit_only(&mut child, inherit).map_err(failed)?; + child.spawn().map(|child| Self { child }).map_err(failed) + } + } + + /// Returns OpenVMM's status if it has exited: `Popen.returncode`, which on Unix is the + /// negated signal of a process that a signal ended, and on Windows the status of + /// `GetExitCodeProcess`, as `std::process::ExitStatus::code` reads it. + fn poll(&mut self) -> Result> { + #[cfg(windows)] + { + let status = self + .process + .wait_timeout(Duration::ZERO) + .map_err(Exit::from_os_error)?; + Ok(status.map(|status| status as i32)) + } + #[cfg(not(windows))] + { + let status = self.child.try_wait().map_err(Exit::from_os_error)?; + Ok(status.map(|status| { + #[cfg(unix)] + { + use std::os::unix::process::ExitStatusExt; + + status + .code() + .unwrap_or_else(|| -status.signal().unwrap_or_default()) + } + #[cfg(not(unix))] + status.code().unwrap_or_default() + })) + } + } +} + /// Ends the OpenVMM of an interrupted run as `subprocess.run` does: on Unix, it waits 0.25 s for /// OpenVMM to exit and then kills it, and on Windows, it waits for OpenVMM to exit. -fn end_interrupted(child: &mut Child) -> Result { - let deadline = std::time::Instant::now() + Duration::from_millis(250); - while poll(child)?.is_none() { - if cfg!(unix) && std::time::Instant::now() >= deadline { - let _ = child.kill(); - let _ = child.wait(); +fn end_interrupted(openvmm: &mut OneShot) -> Result { + #[cfg(not(windows))] + let deadline = Instant::now() + Duration::from_millis(250); + while openvmm.poll()?.is_none() { + #[cfg(not(windows))] + if cfg!(unix) && Instant::now() >= deadline { + let _ = openvmm.child.kill(); + let _ = openvmm.child.wait(); break; } thread::sleep(Duration::from_millis(10)); @@ -848,24 +959,6 @@ fn end_interrupted(child: &mut Child) -> Result { Ok(()) } -/// Returns the status of `child` if it has exited: `Popen.returncode`, which on Unix is the -/// negated signal of a process that a signal ended. -fn poll(child: &mut Child) -> Result> { - let status = child.try_wait().map_err(Exit::from_os_error)?; - Ok(status.map(|status| { - #[cfg(unix)] - { - use std::os::unix::process::ExitStatusExt; - - status - .code() - .unwrap_or_else(|| -status.signal().unwrap_or_default()) - } - #[cfg(not(unix))] - status.code().unwrap_or_default() - })) -} - /// Returns the message of the `OSError` with which `subprocess` fails to start `program`: with /// the program's name on POSIX, and without on Windows. pub(crate) fn spawn_error(error: &std::io::Error, program: &str) -> String { diff --git a/nvx-rs/src/host/cli/sandbox/state.rs b/nvx-rs/src/host/cli/sandbox/state.rs index 7b12284c2..2fdc88c0e 100644 --- a/nvx-rs/src/host/cli/sandbox/state.rs +++ b/nvx-rs/src/host/cli/sandbox/state.rs @@ -15,6 +15,10 @@ //! check. `nvx.py` never wrote one, and `start` refuses a configuration without one. The //! binding is of the layer at the path that the configuration records, which a //! [`RecordedDistro`] holds. +//! - It records the [`ScratchBinding`] of the sandbox's scratch image too, under +//! `scratch_binding`, which [`deserialize_scratch_binding`] reads back for `start` to check and +//! claim, of the image at the path that the configuration records, which a [`RecordedScratch`] +//! holds. `start` refuses a configuration without one as well. //! - Every configuration also records the sandbox's [`SandboxNetwork`], under `network` in the //! serde form of the plan's network, or `null` for no network device, which //! [`deserialize_network`] reads back for `start` to render. It replaces the network options @@ -38,6 +42,7 @@ use crate::host::sandbox::layer::{LayerRole, SandboxLayer}; use crate::host::sandbox::mount::{ MountAccess, MountKind, MountOwner, MountPolicy, SandboxMount, validate_mount_target, }; +use crate::host::sandbox::scratch::ScratchBinding; use crate::host::sandbox::{SandboxLaunch, SandboxNetwork, SandboxSettings}; /// The configuration of a sandbox without shares. @@ -64,6 +69,8 @@ pub(crate) const CONFIG_FORMATS: [u64; 6] = [ ]; /// The member of a configuration that records the binding of the sandbox's distro layer. pub(crate) const DISTRO_BINDING: &str = "distro_binding"; +/// The member of a configuration that records the binding of the sandbox's scratch image. +pub(crate) const SCRATCH_BINDING: &str = "scratch_binding"; /// How deeply a recorded binding may nest its values, which its serde form nests three deep. const MAX_BINDING_DEPTH: usize = 8; /// The member of a configuration that records the sandbox's network, or `null` for none. @@ -297,6 +304,26 @@ impl RecordedDistro { } } +/// The scratch image that `provision` records, at the path that it records for it, with the +/// binding of the image at that path, which `start` checks and claims. +pub(crate) struct RecordedScratch { + /// `path.resolve()` of the image's path, as `provision` records it. + path: String, + /// The binding of the image at that path. + binding: ScratchBinding, +} + +impl RecordedScratch { + /// Returns the scratch image of `launch` at the path that `provision` records, + /// `path.resolve()` of the path given, with a binding of the image there, which claims + /// nothing. + pub(crate) fn new(launch: &SandboxLaunch) -> Result { + let path = resolved(launch.scratch())?; + let binding = ScratchBinding::bind(std::path::Path::new(&path))?; + Ok(Self { path, binding }) + } +} + /// Returns a list of strings. fn strings(values: &[String]) -> Value { Value::List( @@ -309,10 +336,12 @@ fn strings(values: &[String]) -> Value { /// Returns the configuration that `provision` records for `launch` and the other options, as /// `_serialize_launch` builds it, with `distro`, the launch's distro layer at the path that -/// `provision` verified and records, and its binding. +/// `provision` verified and records, and its binding, and `scratch`, the launch's scratch image at +/// the path that `provision` records, and its binding. pub(crate) fn serialize_launch( launch: &SandboxLaunch, distro: &RecordedDistro, + scratch: &RecordedScratch, config: &ProvisionConfig<'_>, ) -> Result { let format = config_format(launch.mounts()); @@ -336,7 +365,7 @@ pub(crate) fn serialize_launch( let mut value = dict([ ("format", Value::from(format)), ("layers", Value::List(layers)), - ("scratch", Value::from(resolved(launch.scratch())?)), + ("scratch", Value::from(scratch.path.as_str())), ("hostname", Value::from(settings.hostname.as_str())), ("workload_uid", Value::from(uid)), ("workload_gid", Value::from(gid)), @@ -356,6 +385,12 @@ pub(crate) fn serialize_launch( .and_then(python_value) .ok_or_else(|| Exit::error("cannot record the distro binding"))?; members.insert(DISTRO_BINDING.to_owned(), binding); + let binding = serde_json::to_value(&scratch.binding) + .ok() + .as_ref() + .and_then(python_value) + .ok_or_else(|| Exit::error("cannot record the scratch binding"))?; + members.insert(SCRATCH_BINDING.to_owned(), binding); if lists_mounts(format) { let mut mounts = Vec::new(); for mount in launch.mounts() { @@ -438,11 +473,31 @@ fn json_value(document: &Document, id: NodeId, depth: usize) -> Option Result { - let Some(binding) = member(document, document.root(), DISTRO_BINDING) else { - return Err(Exit::error( - "sandbox configuration has no distro binding; deprovision the sandbox and provision \ - it again", - )); + deserialize_recorded(document, DISTRO_BINDING, "distro binding") +} + +/// Reads the binding of the scratch image that a configuration records, which `start` checks and +/// claims. +/// +/// A configuration without one, as earlier releases of `nvx-rs` wrote, fails as `sandbox +/// configuration has no scratch binding`, and one that is not of the binding's serde form as +/// `sandbox configuration is malformed`, as [`deserialize_binding`] fails. +pub(crate) fn deserialize_scratch_binding(document: &Document) -> Result { + deserialize_recorded(document, SCRATCH_BINDING, "scratch binding") +} + +/// Reads the serde form of the member `name` of a configuration, a binding that messages call +/// `description`, as [`deserialize_binding`] reads that of the distro layer. +fn deserialize_recorded( + document: &Document, + name: &str, + description: &str, +) -> Result { + let Some(binding) = member(document, document.root(), name) else { + return Err(Exit::error(format!( + "sandbox configuration has no {description}; deprovision the sandbox and provision it \ + again" + ))); }; json_value(document, binding, MAX_BINDING_DEPTH) .and_then(|value| serde_json::from_value(value).ok()) diff --git a/nvx-rs/src/host/mod.rs b/nvx-rs/src/host/mod.rs index 0685cb868..3e00d9d4f 100644 --- a/nvx-rs/src/host/mod.rs +++ b/nvx-rs/src/host/mod.rs @@ -29,7 +29,10 @@ //! - [`sandbox`] is the launch profile of `nvx.py sandbox`: a container over read-only EROFS //! layers and a private ext4 scratch disk, with live shares, and the checks of its options. //! [`sandbox::distro`] binds a distro layer to what explicit verification found, so that a -//! launch checks the layer through [`seal`], which seals open files from their metadata alone. +//! launch checks the layer through [`seal`], which seals open files from their metadata alone, +//! and [`sandbox::scratch`] binds and claims a scratch image. A +//! [`PreparedLaunch`](sandbox::PreparedLaunch) hands the VMM the files that those checks +//! opened, through the [`supervisor`]'s allowlist on Linux. //! - `cli` (feature `cli`) is the `nvx-rs` command-line interface, which `nvx.py` runs for the //! steps that it delegates: artifact lookup, egress policy compilation, and sandboxes. //! diff --git a/nvx-rs/src/host/platform/linux.rs b/nvx-rs/src/host/platform/linux.rs index 2d7d31073..2125e5025 100644 --- a/nvx-rs/src/host/platform/linux.rs +++ b/nvx-rs/src/host/platform/linux.rs @@ -266,6 +266,24 @@ pub(crate) fn boot_id() -> io::Result<[u8; 16]> { /// command's own standard streams replace in the new process, and for a descriptor that is not /// close-on-exec in the caller, which any process that the caller spawns would inherit. pub(crate) fn detach(command: &mut Command, inherit: &[Inherited<'_>]) -> io::Result<()> { + sys::detach(command, allowlist(inherit)?); + Ok(()) +} + +/// Starts the command in the caller's session, as a child that shares the caller's terminal, with +/// the descriptors `inherit` under their numbers in the caller, and no other descriptor of the +/// caller beyond the standard streams that the command sets up, as [`detach`] does. +/// +/// Fails as [`detach`] does. +#[cfg(feature = "cli")] +pub(crate) fn inherit_only(command: &mut Command, inherit: &[Inherited<'_>]) -> io::Result<()> { + sys::inherit_only(command, allowlist(inherit)?); + Ok(()) +} + +/// Returns the numbers of the descriptors `inherit`, each once, after checking that a spawn can +/// pass them to its process alone, as [`detach`] says. +fn allowlist(inherit: &[Inherited<'_>]) -> io::Result> { use nix::fcntl::{FcntlArg, FdFlag, fcntl}; let mut descriptors: Vec = Vec::with_capacity(inherit.len()); @@ -295,8 +313,7 @@ pub(crate) fn detach(command: &mut Command, inherit: &[Inherited<'_>]) -> io::Re descriptors.push(descriptor); } } - sys::detach(command, descriptors); - Ok(()) + Ok(descriptors) } /// Checks that the hypervisor device is present and accessible. diff --git a/nvx-rs/src/host/platform/other.rs b/nvx-rs/src/host/platform/other.rs index 89fdf6a03..8d32425a8 100644 --- a/nvx-rs/src/host/platform/other.rs +++ b/nvx-rs/src/host/platform/other.rs @@ -116,6 +116,12 @@ pub(crate) fn detach(_command: &mut Command, inherit: &[Inherited<'_>]) -> io::R } } +/// Leaves the command as it is, unless it should inherit descriptors, as [`detach`] does. +#[cfg(feature = "cli")] +pub(crate) fn inherit_only(command: &mut Command, inherit: &[Inherited<'_>]) -> io::Result<()> { + detach(command, inherit) +} + pub(crate) fn probe_hypervisor(_hypervisor: Hypervisor) -> Result<(), String> { Err(unsupported().to_string()) } diff --git a/nvx-rs/src/host/platform/windows.rs b/nvx-rs/src/host/platform/windows.rs index ff92da84a..9f1607917 100644 --- a/nvx-rs/src/host/platform/windows.rs +++ b/nvx-rs/src/host/platform/windows.rs @@ -9,6 +9,8 @@ use std::sync::{Mutex, MutexGuard, PoisonError}; use std::time::{Duration, Instant}; use std::{mem, ptr}; +#[cfg(feature = "cli")] +use windows_sys::Win32::Foundation::{DUPLICATE_SAME_ACCESS, DuplicateHandle}; use windows_sys::Win32::Foundation::{ ERROR_ACCESS_DENIED, ERROR_ALREADY_EXISTS, ERROR_BROKEN_PIPE, ERROR_FILE_NOT_FOUND, ERROR_INVALID_PARAMETER, ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_NO_DATA, @@ -34,7 +36,10 @@ use windows_sys::Win32::Storage::FileSystem::{ READ_CONTROL, ReadFile, SECURITY_IDENTIFICATION, SECURITY_SQOS_PRESENT, WriteFile, }; #[cfg(feature = "cli")] -use windows_sys::Win32::System::Console::{CTRL_C_EVENT, SetConsoleCtrlHandler}; +use windows_sys::Win32::System::Console::{ + CTRL_C_EVENT, GetStdHandle, STD_ERROR_HANDLE, STD_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, + SetConsoleCtrlHandler, +}; use windows_sys::Win32::System::IO::{CancelIoEx, GetOverlappedResult, OVERLAPPED}; use windows_sys::Win32::System::LibraryLoader::{ GetProcAddress, LOAD_LIBRARY_SEARCH_SYSTEM32, LoadLibraryExW, @@ -48,9 +53,10 @@ use windows_sys::Win32::System::Threading::{ CREATE_UNICODE_ENVIRONMENT, CreateEventW, CreateProcessW, DeleteProcThreadAttributeList, EXTENDED_STARTUPINFO_PRESENT, GetCurrentProcess, GetProcessTimes, INFINITE, InitializeProcThreadAttributeList, LPPROC_THREAD_ATTRIBUTE_LIST, OpenProcess, OpenProcessToken, - PROC_THREAD_ATTRIBUTE_HANDLE_LIST, PROCESS_INFORMATION, PROCESS_QUERY_LIMITED_INFORMATION, - PROCESS_SYNCHRONIZE, PROCESS_TERMINATE, STARTF_USESTDHANDLES, STARTUPINFOEXW, TerminateProcess, - UpdateProcThreadAttribute, WaitForSingleObject, + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, PROCESS_CREATION_FLAGS, PROCESS_INFORMATION, + PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_SYNCHRONIZE, PROCESS_TERMINATE, + STARTF_USESTDHANDLES, STARTUPINFOEXW, TerminateProcess, UpdateProcThreadAttribute, + WaitForSingleObject, }; use super::Transport; @@ -461,6 +467,8 @@ impl Drop for InheritableForSpawn { /// same handle restore what it was before either. A process that the caller spawns concurrently /// without a handle list, as `std::process` does, could inherit them in that window; a process /// that it spawns with one could not. +/// +/// Fails with [`io::ErrorKind::InvalidInput`] for a batch file, as [`refuse_batch_file`] says. pub(crate) fn spawn_detached( program: &Path, arguments: &[OsString], @@ -469,16 +477,103 @@ pub(crate) fn spawn_detached( inherit: &[Inherited<'_>], breakaway_from_job: bool, ) -> io::Result { + let mut flags = CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW; + if breakaway_from_job { + flags |= CREATE_BREAKAWAY_FROM_JOB; + } + spawn_listed( + program, + arguments, + Some(working_dir), + stdio.map(Some), + inherit, + flags, + ) +} + +/// Starts OpenVMM in the caller's console, process group, and working directory, as +/// `subprocess.run` starts a child, with copies of the caller's standard handles as its own. +/// +/// Unlike `std::process::Command`, which lets the child inherit every inheritable handle of the +/// caller, it passes the child no other handle of the caller than the handles `inherit`, which +/// keep their values in the child, through the handle list of [`spawn_detached`], with the same +/// window for processes that the caller spawns concurrently without one. Unlike +/// `std::process::Command` too, which escapes the arguments of a batch file for `cmd.exe`, it +/// fails with [`io::ErrorKind::InvalidInput`] for a batch file, as [`refuse_batch_file`] says. +#[cfg(feature = "cli")] +pub(crate) fn spawn_foreground( + program: &Path, + arguments: &[OsString], + inherit: &[Inherited<'_>], +) -> io::Result { + let stdio = [ + standard_handle(STD_INPUT_HANDLE)?, + standard_handle(STD_OUTPUT_HANDLE)?, + standard_handle(STD_ERROR_HANDLE)?, + ]; + spawn_listed(program, arguments, None, stdio, inherit, 0) +} + +/// Returns a copy of the caller's standard handle `which`, or `None` if the caller has none, as +/// `std::process::Command` gives a child the standard handles of its caller. +#[cfg(feature = "cli")] +fn standard_handle(which: STD_HANDLE) -> io::Result> { + // SAFETY: GetStdHandle has no preconditions. + let handle = unsafe { GetStdHandle(which) }; + if handle.is_null() || handle == INVALID_HANDLE_VALUE { + return Ok(None); + } + let mut copy = ptr::null_mut(); + // SAFETY: the source and target are this process, and the output pointer is valid; a + // standard handle that is not open fails the copy. + if unsafe { + DuplicateHandle( + GetCurrentProcess(), + handle, + GetCurrentProcess(), + &mut copy, + 0, + 0, + DUPLICATE_SAME_ACCESS, + ) + } == 0 + { + return Err(io::Error::last_os_error()); + } + Ok(owned(copy)) +} + +/// Starts `program` with `arguments` in `working_dir`, or in the caller's working directory, with +/// the creation flags `flags`, the handles `stdio` as its standard handles, a missing one null, +/// and the caller's handles `inherit`, and no other handle of the caller, as [`spawn_detached`] +/// describes. Fails with [`io::ErrorKind::InvalidInput`] for a batch file, as +/// [`refuse_batch_file`] says. +fn spawn_listed( + program: &Path, + arguments: &[OsString], + working_dir: Option<&Path>, + stdio: [Option; 3], + inherit: &[Inherited<'_>], + flags: PROCESS_CREATION_FLAGS, +) -> io::Result { + refuse_batch_file(program)?; let application = wide(program); let mut command_line = command_line(program.as_os_str(), arguments)?; - let directory = wide(working_dir); - let mut handles = stdio.each_ref().map(raw).to_vec(); + let directory = working_dir.map(wide); + let standard = stdio + .each_ref() + .map(|handle| handle.as_ref().map_or(ptr::null_mut(), raw)); + let mut handles: Vec = standard + .into_iter() + .filter(|handle| !handle.is_null()) + .collect(); for handle in &handles { // SAFETY: the handle is open and owned by `stdio`. if unsafe { SetHandleInformation(*handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) } == 0 { return Err(io::Error::last_os_error()); } } + let standard_handles = handles.len(); for inherited in inherit { let handle = inherited.as_handle().as_raw_handle() as HANDLE; // A handle that the list names twice fails the spawn. @@ -486,37 +581,40 @@ pub(crate) fn spawn_detached( handles.push(handle); } } - let inheritable = InheritableForSpawn::new(&handles[stdio.len()..])?; - let mut attributes = AttributeList::new(1)?; - // SAFETY: the list has room for one attribute, and `handles` outlives CreateProcessW. - if unsafe { - UpdateProcThreadAttribute( - attributes.as_ptr(), - 0, - PROC_THREAD_ATTRIBUTE_HANDLE_LIST as usize, - handles.as_ptr().cast(), - mem::size_of_val(handles.as_slice()), - ptr::null_mut(), - ptr::null(), - ) - } == 0 - { - return Err(io::Error::last_os_error()); - } + let inheritable = InheritableForSpawn::new(&handles[standard_handles..])?; + // A list cannot be empty: a child that inherits no handle at all is created without one, and + // without inheritance. + let mut attributes = if handles.is_empty() { + None + } else { + Some(AttributeList::new(1)?) + }; // SAFETY: STARTUPINFOEXW is a plain C structure for which all-zero is a valid value. let mut startup: STARTUPINFOEXW = unsafe { mem::zeroed() }; startup.StartupInfo.cb = mem::size_of::() as u32; - startup.StartupInfo.dwFlags = STARTF_USESTDHANDLES; - startup.StartupInfo.hStdInput = handles[0]; - startup.StartupInfo.hStdOutput = handles[1]; - startup.StartupInfo.hStdError = handles[2]; - startup.lpAttributeList = attributes.as_ptr(); - let mut flags = CREATE_UNICODE_ENVIRONMENT - | EXTENDED_STARTUPINFO_PRESENT - | CREATE_NEW_PROCESS_GROUP - | CREATE_NO_WINDOW; - if breakaway_from_job { - flags |= CREATE_BREAKAWAY_FROM_JOB; + let mut flags = flags | CREATE_UNICODE_ENVIRONMENT; + if let Some(attributes) = &mut attributes { + // SAFETY: the list has room for one attribute, and `handles` outlives CreateProcessW. + if unsafe { + UpdateProcThreadAttribute( + attributes.as_ptr(), + 0, + PROC_THREAD_ATTRIBUTE_HANDLE_LIST as usize, + handles.as_ptr().cast(), + mem::size_of_val(handles.as_slice()), + ptr::null_mut(), + ptr::null(), + ) + } == 0 + { + return Err(io::Error::last_os_error()); + } + startup.StartupInfo.dwFlags = STARTF_USESTDHANDLES; + startup.StartupInfo.hStdInput = standard[0]; + startup.StartupInfo.hStdOutput = standard[1]; + startup.StartupInfo.hStdError = standard[2]; + startup.lpAttributeList = attributes.as_ptr(); + flags |= EXTENDED_STARTUPINFO_PRESENT; } // SAFETY: PROCESS_INFORMATION is a plain C structure for which all-zero is a valid value. let mut information: PROCESS_INFORMATION = unsafe { mem::zeroed() }; @@ -528,10 +626,12 @@ pub(crate) fn spawn_detached( command_line.as_mut_ptr(), ptr::null(), ptr::null(), - 1, + i32::from(attributes.is_some()), flags, ptr::null(), - directory.as_ptr(), + directory + .as_ref() + .map_or(ptr::null(), |directory| directory.as_ptr()), &startup.StartupInfo, &mut information, ) @@ -555,6 +655,34 @@ pub(crate) fn spawn_detached( }) } +/// Refuses `program` if it is a batch file, which `CreateProcessW` runs through `cmd.exe`. +/// `cmd.exe` parses the command line by rules of its own, under which an argument that holds `&` +/// or `|`, as a path may, starts another command, and the quoting of [`command_line`], which +/// follows the C runtime's rules, does not prevent that. Tells a batch file as `std::process` does, +/// by the last four characters of the program's full path name: `.bat` or `.cmd` in any case, so +/// a file named only `.cmd` is one too. +fn refuse_batch_file(program: &Path) -> io::Result<()> { + let full: Vec = std::path::absolute(program)? + .as_os_str() + .encode_wide() + .collect(); + let suffix: Vec = full[full.len().saturating_sub(4)..] + .iter() + .map(|unit| u8::try_from(*unit).map_or(0, |byte| byte.to_ascii_lowercase())) + .collect(); + if suffix == b".bat" || suffix == b".cmd" { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!( + "cannot start the batch file {}: Windows runs batch files through cmd.exe, which \ + would read their arguments as shell syntax", + program.display() + ), + )); + } + Ok(()) +} + /// Builds a NUL-terminated command line that the Microsoft C runtime splits back into /// `program` and `arguments`. fn command_line(program: &OsStr, arguments: &[OsString]) -> io::Result> { @@ -1439,6 +1567,79 @@ mod tests { assert!(!allowed_inheritable && kept_inheritable); } + #[cfg(feature = "cli")] + #[test] + fn foreground_children_inherit_only_the_allowed_handles_besides_their_standard_ones() { + // A handle that this process leaves inheritable, as its parent may have passed it, and + // that `std::process::Command` would pass on. + let (_other_reader, other) = io::pipe().unwrap(); + make_inheritable(&other); + let (_allowed_reader, allowed) = io::pipe().unwrap(); + let system = std::env::var_os("SystemRoot").unwrap(); + let cmd = Path::new(&system).join("System32").join("cmd.exe"); + // The child shares this process's console and standard handles, so it writes nothing. + let child = spawn_foreground( + &cmd, + &["/c", "ping -n 30 127.0.0.1 >NUL"].map(OsString::from), + &[Inherited::new(&allowed)], + ) + .unwrap(); + let value = |handle: &io::PipeWriter| handle.as_raw_handle() as HANDLE; + let (allowed_held, other_held) = ( + holds(&child.process, value(&allowed), &allowed), + holds(&child.process, value(&other), &other), + ); + let allowed_inheritable = inheritable(&allowed); + assert!(child.terminate(Duration::from_secs(10))); + assert!(allowed_held, "an allowed handle was not inherited"); + assert!(!other_held, "a handle that was not allowed was inherited"); + assert!( + !allowed_inheritable, + "the spawn left the allowed handle inheritable" + ); + } + + #[test] + fn batch_files_are_refused_before_cmd_could_read_their_arguments() { + // Batch files that would leave a marker if they ran, with an argument that cmd.exe would + // read as a second command. + let directory = tempfile::tempdir().unwrap(); + let marker = directory.path().join("ran"); + let injected = directory.path().join("injected"); + let arguments = [OsString::from(format!("x&echo>{}", injected.display()))]; + // A file named only `.cmd` is a batch file too, and Windows drops the trailing dot of a + // name, so the last path names `trailing.cmd`. + for (name, program) in [ + ("plain.cmd", "plain.cmd"), + ("SHOUT.BAT", "SHOUT.BAT"), + (".cmd", ".cmd"), + ("trailing.cmd", "trailing.cmd."), + ] { + let script = format!("@echo ran>\"{}\"\r\n", marker.display()); + std::fs::write(directory.path().join(name), script).unwrap(); + let program = directory.path().join(program); + let refused = |result: io::Result| { + let error = result.err().expect("a batch file started"); + assert_eq!(error.kind(), io::ErrorKind::InvalidInput, "{error}"); + }; + #[cfg(feature = "cli")] + refused(spawn_foreground(&program, &arguments, &[])); + let (stdin, _stdin_writer) = io::pipe().unwrap(); + let (_stdout_reader, stdout) = io::pipe().unwrap(); + let stderr = stdout.try_clone().unwrap(); + refused(spawn_detached( + &program, + &arguments, + directory.path(), + [stdin.into(), stdout.into(), stderr.into()], + &[], + false, + )); + } + std::thread::sleep(Duration::from_millis(500)); + assert!(!marker.exists() && !injected.exists()); + } + #[test] fn concurrent_spawns_that_allow_one_handle_all_inherit_it_and_restore_it() { /// Threads that spawn at once. diff --git a/nvx-rs/src/host/sandbox/distro.rs b/nvx-rs/src/host/sandbox/distro.rs index b47e20518..a5d0f87f6 100644 --- a/nvx-rs/src/host/sandbox/distro.rs +++ b/nvx-rs/src/host/sandbox/distro.rs @@ -179,10 +179,19 @@ pub struct DistroBinding { /// later takes its path. On Windows, it denies other opens for writing or deleting the layer, so /// that nothing modifies, renames, or replaces the layer while it is held; OpenVMM's read-only /// open of the layer shares it. Dropping it closes the file. +/// +/// A [`PreparedLaunch`](super::PreparedLaunch) takes it, with the path at which the check opened +/// the layer and the format and identity that the binding names, and hands the layer to the VMM. #[derive(Debug)] pub struct CheckedDistro { /// The checked layer. file: File, + /// The path at which the check opened the layer. + path: PurePath, + /// The layer's format, as the binding names it. + format: DistroFormat, + /// The identity that the guest expects of the layer, as the binding names it. + identity: String, } impl CheckedDistro { @@ -191,6 +200,23 @@ impl CheckedDistro { &self.file } + /// Returns the path at which the check opened the layer. + pub fn path(&self) -> &Path { + self.path.as_path() + } + + /// Returns the layer's format and the identity that the guest expects of it, as the binding + /// names them. + pub(crate) fn bound(&self) -> (DistroFormat, &str) { + (self.format, &self.identity) + } + + /// Returns the checked layer, for a prepared launch to move. + #[cfg(target_os = "linux")] + pub(crate) fn file_mut(&mut self) -> &mut File { + &mut self.file + } + /// Returns the checked layer, which the caller then holds: an owned descriptor on Linux, and /// an owned handle on Windows, which it converts into an `OwnedFd` or an `OwnedHandle`. pub fn into_file(self) -> File { @@ -376,7 +402,12 @@ impl DistroBinding { } }, } - Ok(CheckedDistro { file }) + Ok(CheckedDistro { + file, + path: path.clone(), + format: self.format, + identity: self.identity.clone(), + }) } } diff --git a/nvx-rs/src/host/sandbox/mod.rs b/nvx-rs/src/host/sandbox/mod.rs index df7f577da..7571bd71b 100644 --- a/nvx-rs/src/host/sandbox/mod.rs +++ b/nvx-rs/src/host/sandbox/mod.rs @@ -25,6 +25,12 @@ //! environment. //! - [`distro`]: the binding of the distro layer, which explicit verification makes from the //! layer and its manifest, and which a launch checks from metadata alone. +//! - [`scratch`]: the binding of the scratch image, its identity, kind, and length, which a +//! launch checks from metadata alone, and the claim of the image by one sandbox at a time. +//! - [`PreparedLaunch`]: the launch of a plan with the files that the checks of its block devices +//! returned, which hands the VMM those objects rather than paths that something could replace +//! meanwhile: the OpenVMM arguments that name them, and the descriptors that the VMM inherits; +//! [`prepared`] explains the handoff on each host. //! //! Values are checked as Python checked them: whitespace is what `str.isspace` accepts, //! integers are what `int` reads ([`Int`]), UUIDs are what `uuid.UUID` reads, host paths are @@ -44,6 +50,8 @@ pub mod layer; pub mod mount; pub mod options; pub mod plan; +pub mod prepared; +pub mod scratch; use std::path::Path; @@ -53,6 +61,7 @@ pub use self::mount::{MountAccess, MountKind, MountOwner, MountPolicy, PolicyKin pub use self::plan::{ BlockRole, PLAN_VERSION, SandboxDistro, SandboxNetwork, SandboxPlan, SandboxUpper, }; +pub use self::prepared::PreparedLaunch; use crate::error::{Error, Result}; pub use crate::host::python::builtins::{Int, IntError}; use crate::host::python::builtins::{is_space, repr}; @@ -126,12 +135,11 @@ pub fn require_file(path: &Path, description: &str) -> Result<()> { } /// Refuses a disk path that OpenVMM's `--microvm-sandbox-block` value could not hold: one with -/// a comma or semicolon. -fn reject_disk_path(path: &PurePath) -> Result<()> { - let value = path.as_str(); - if value.contains([',', ';']) { +/// a comma or semicolon. The message shows `path` as `shown`. +fn reject_disk_path(path: &PurePath, shown: &str) -> Result<()> { + if path.as_str().contains([',', ';']) { return Err(refused(format!( - "disk paths containing commas or semicolons are unsupported: {value}" + "disk paths containing commas or semicolons are unsupported: {shown}" ))); } Ok(()) diff --git a/nvx-rs/src/host/sandbox/plan.rs b/nvx-rs/src/host/sandbox/plan.rs index 503ce5d0e..08716fdb4 100644 --- a/nvx-rs/src/host/sandbox/plan.rs +++ b/nvx-rs/src/host/sandbox/plan.rs @@ -22,7 +22,9 @@ //! scratch block device for a RAM upper, and the network device, and //! [`kernel_command_line`](SandboxPlan::kernel_command_line) describes the root with the boot //! tokens of [`protocol::root`](crate::protocol::root), within the budget that OpenVMM leaves the -//! profile. +//! profile. Its arguments name the block devices by their paths; a launch renders them through a +//! [`PreparedLaunch`](super::PreparedLaunch), which names the files that the checks of the images +//! opened instead. //! //! A plan is persistable. Its serde form, of version [`PLAN_VERSION`], requires every field and //! refuses unknown ones and other versions, and reading it checks the plan as [`SandboxPlan::new`] @@ -449,7 +451,7 @@ impl SandboxPlan { BlockRole::Scratch => "ext4 scratch image", }; require_pure_file(path, description)?; - reject_disk_path(path)?; + reject_disk_path(path, path.as_str())?; reject_container_path(path, path.as_str())?; // OpenVMM reads the extension of the path that it is given, which for a provisioned // sandbox is the resolved one: a link must not resolve to a container's name either. @@ -480,12 +482,23 @@ impl SandboxPlan { pub(crate) fn openvmm_arguments_with( &self, cwd: &dyn Fn() -> io::Result, + ) -> Result> { + self.render_arguments(&|_, path| path.as_str().to_owned(), cwd) + } + + /// Renders the OpenVMM arguments of [`openvmm_arguments`](Self::openvmm_arguments), with + /// `block` naming the file of each block device, given its role and its path in the plan, and + /// `cwd` as `Path.cwd()`. + pub(crate) fn render_arguments( + &self, + block: &dyn Fn(BlockRole, &PurePath) -> String, + cwd: &dyn Fn() -> io::Result, ) -> Result> { let mut arguments = vec!["--machine".to_owned(), "microvm".to_owned()]; for (role, path) in self.pure_blocks() { let access = if role.is_read_only() { ",ro" } else { "" }; arguments.push("--microvm-sandbox-block".to_owned()); - arguments.push(format!("{role}:file:{}{access}", path.as_str())); + arguments.push(format!("{role}:file:{}{access}", block(role, path))); } let (uid, gid) = self.settings.workload_identity; arguments.extend([ @@ -586,13 +599,19 @@ impl SandboxPlan { /// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) for an identity that is /// not a UUID in its canonical text. pub(crate) fn bound_image(binding: &DistroBinding) -> Result { - let identity = Uuid::parse(binding.identity()).ok_or_else(|| { + image_of(binding.format(), binding.identity()) +} + +/// Returns the image of a layer of `format` whose binding names `identity`, as [`bound_image`] +/// does. +pub(crate) fn image_of(format: DistroFormat, identity: &str) -> Result { + let identity = Uuid::parse(identity).ok_or_else(|| { refused(format!( "the distro binding names the identity {}, which is not a UUID in its canonical text", - repr(binding.identity()) + repr(identity) )) })?; - Ok(match binding.format() { + Ok(match format { DistroFormat::Erofs => RootImage::erofs(Some(identity)), }) } diff --git a/nvx-rs/src/host/sandbox/prepared.rs b/nvx-rs/src/host/sandbox/prepared.rs new file mode 100644 index 000000000..381c59c16 --- /dev/null +++ b/nvx-rs/src/host/sandbox/prepared.rs @@ -0,0 +1,355 @@ +//! The prepared launch of a sandbox: the files that the checks of its plan returned, held open +//! until its VMM has them, the descriptors or handles that the VMM inherits, and the OpenVMM +//! arguments and kernel command line that name them. +//! +//! A [`SandboxPlan`] names its block devices by path, and persists. A launch that gave the VMM +//! those paths would let anything that took a path's place after the check, and before the VMM +//! opened the path, take the image's place. A [`PreparedLaunch`] instead takes the files that the +//! checks returned, the [`CheckedDistro`] of +//! [`DistroBinding::check`](super::distro::DistroBinding::check) and, for an upper on scratch, +//! the [`CheckedScratch`] of [`ScratchBinding::check`](super::scratch::ScratchBinding::check), and +//! hands the VMM exactly those objects: +//! +//! - On Linux, the VMM inherits the checked descriptors, through the allowlist of +//! [`VmmCommand::inherit`](crate::host::supervisor::VmmCommand::inherit), and the OpenVMM +//! arguments name each `/proc/self/fd/N`, which OpenVMM opens: the link reaches the checked file +//! whatever later takes its path. No other descriptor of the caller reaches the VMM. A checked +//! file on a standard stream's descriptor, which no spawn can pass under its number, as when the +//! caller had closed that stream, first moves above the standard streams, with its open file +//! description. +//! - On Windows, the arguments name each checked file by its final path, the absolute `\\?\` path +//! that the file's handle gives, which resolves every link and junction that the plan's path +//! went through, and does not depend on the VMM's directory. The checked files hold that path in +//! place until they are closed: the distro layer's file denies other opens for writing or +//! deleting the layer, the scratch image's guard denies deleting or renaming it, and NTFS renames +//! no directory while a file below it is open. OpenVMM's read-only open of the layer, and its +//! open of the scratch image for reading and writing, share them. A caller that ends before the +//! VMM has opened the images, as when it crashes, closes the checked files early, so that a +//! replacement at their paths would then reach the VMM unchecked; +//! [#780](https://github.com/microsoft/nvx/issues/780) tracks holding the paths in that case. +//! The VMM inherits one handle, which no argument names: the claim's handle of the scratch +//! image, so that it holds the claim from its start, even if the caller ends before the VMM has +//! opened the image. A final path that OpenVMM could not take as the path of a raw image, with a +//! comma or semicolon or a container's extension, which a link on the way, the directory of a +//! relative path, or the case of a name on disk can bring, fails the preparation. +//! - Other hosts cannot check files, so no launch is prepared there. +//! +//! Images are raw: OpenVMM attaches a file's bytes as they are, and the launch never converts an +//! image or reads its footer, as [`SandboxDistro`](super::SandboxDistro) says. +//! +//! The paths of descriptors exist only in the launch: [`PreparedLaunch::openvmm_arguments`] +//! renders them, and the plan, which callers persist, keeps the real paths. The caller adds its own +//! arguments, such as its boot console, CPU profile, and guest control, starts the VMM with +//! [`PreparedLaunch::inherited`] as its allowlist, and holds the prepared launch until the guest +//! reports authenticated Ready, or until it has torn down a launch that failed. Dropping it then +//! closes the checked files, which releases the guards, while the VMM keeps its own opens of the +//! images, and the claim of the scratch image, which it inherited. +//! +//! A plan never names a root identity for the workload: [`SandboxPlan::new`] refuses a zero UID +//! or GID, so the VMM's `--microvm-workload-identity` never encodes root. + +use std::fs::File; +use std::io; +use std::path::Path; + +use super::distro::CheckedDistro; +#[cfg(any(target_os = "linux", windows))] +use super::mount::os_error; +use super::plan::{self, BlockRole, SandboxPlan}; +use super::refused; +use super::scratch::CheckedScratch; +#[cfg(windows)] +use super::{reject_container_path, reject_disk_path}; +use crate::error::Result; +use crate::host::python::fs; +use crate::host::python::path::PurePath; +#[cfg(windows)] +use crate::host::seal; +use crate::host::supervisor::Inherited; +#[cfg(target_os = "linux")] +use crate::host::sys; + +/// A launch that holds the checked files of its plan, and the OpenVMM arguments and kernel +/// command line that hand them to the VMM. +/// +/// [`new`](Self::new) prepares it from a plan and the files that the checks of its block devices +/// returned. The caller then starts the VMM with [`openvmm_arguments`](Self::openvmm_arguments), +/// its own arguments, and [`kernel_command_line`](Self::kernel_command_line), with +/// [`inherited`](Self::inherited) as the allowlist of its +/// [`VmmCommand`](crate::host::supervisor::VmmCommand), and drops the prepared launch once the +/// guest reports authenticated Ready, or once a launch that failed is torn down: +/// +/// ```no_run +/// use std::ffi::OsString; +/// use std::fs::File; +/// use std::time::Instant; +/// +/// use nvx::host::sandbox::distro::{DISTRO_POLICY_REVISION, DistroBinding}; +/// use nvx::host::sandbox::scratch::{CLAIM_RELEASE_WAIT, ScratchBinding}; +/// use nvx::host::sandbox::{PreparedLaunch, SandboxLaunch}; +/// use nvx::host::supervisor::{self, VmmCommand}; +/// +/// # fn launch( +/// # launch: &SandboxLaunch, +/// # distro_binding: &DistroBinding, +/// # scratch_binding: &ScratchBinding, +/// # capability: &[u8; nvx::protocol::CAPABILITY_LEN], +/// # ) -> Result<(), Box> { +/// // Check the bindings that the caller recorded when it staged the images: from their metadata +/// // alone, and claiming the scratch image for this sandbox. +/// let distro = distro_binding.check(launch.distro(), DISTRO_POLICY_REVISION)?; +/// let scratch = scratch_binding.check(launch.scratch(), Instant::now() + CLAIM_RELEASE_WAIT)?; +/// let prepared = PreparedLaunch::new(launch.bound_plan(distro_binding)?, distro, Some(scratch), "")?; +/// +/// let mut arguments: Vec = +/// prepared.openvmm_arguments().iter().map(OsString::from).collect(); +/// arguments.extend(["--cmdline".into(), prepared.kernel_command_line().into()]); +/// // ... the caller's own arguments: hypervisor, memory, kernel, initrd, and guest control ... +/// let mut command = VmmCommand::new("openvmm", arguments, "."); +/// command.inherit = prepared.inherited(); +/// let launched = supervisor::spawn(&command, capability, File::create("openvmm.log")?)?; +/// // ... record the VMM, and wait for the guest's authenticated Ready ... +/// drop(prepared); +/// launched.detach(); +/// # Ok(()) +/// # } +/// ``` +#[derive(Debug)] +pub struct PreparedLaunch { + /// The plan, which names the images by their real paths. + plan: SandboxPlan, + /// The checked distro layer. + distro: CheckedDistro, + /// The checked scratch image, for an upper on scratch. + scratch: Option, + /// The OpenVMM arguments of the plan, which name the checked files. + openvmm_arguments: Vec, + /// The kernel command line of the plan. + kernel_command_line: String, +} + +impl PreparedLaunch { + /// Prepares the launch of `plan` with the checked files of its block devices: `distro`, which + /// [`DistroBinding::check`](super::distro::DistroBinding::check) returned for the plan's + /// image, and `scratch`, which [`ScratchBinding::check`](super::scratch::ScratchBinding::check) + /// returned for the scratch image of an upper on scratch, or `None` for an upper in RAM. It + /// renders the plan's OpenVMM arguments, naming the checked files, and its kernel command line + /// with `user_command_line`, as [`SandboxPlan::kernel_command_line`] does. + /// + /// The checked files must be those of the plan: the distro layer checked at the path of the + /// plan's image, with the format and identity of its image, and the scratch image checked at + /// the path of its upper. Fails with + /// [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) for files that are not, and as + /// [`SandboxPlan::openvmm_arguments`] and [`SandboxPlan::kernel_command_line`] fail; on + /// Windows, also with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) for a checked + /// file whose final path has a comma or semicolon, or ends in `.vhd`, `.vhdx`, `.vmgs`, or + /// `.iso`, which OpenVMM could not attach as a raw image; and with + /// [`ErrorKind::Io`](crate::ErrorKind::Io) if a checked file cannot be moved above the + /// standard streams on Linux, or its final path is unavailable on Windows. It never opens, + /// reads, or maps a file. + pub fn new( + plan: SandboxPlan, + distro: CheckedDistro, + scratch: Option, + user_command_line: &str, + ) -> Result { + Self::new_with(plan, distro, scratch, user_command_line, &fs::cwd) + } + + /// Implements [`new`](Self::new) with `cwd` as `Path.cwd()`. + pub(crate) fn new_with( + plan: SandboxPlan, + distro: CheckedDistro, + scratch: Option, + user_command_line: &str, + cwd: &dyn Fn() -> io::Result, + ) -> Result { + let planned = plan.distro().path(); + if !same_path(distro.path(), planned) { + return Err(refused(format!( + "the checked distro layer {} is not the plan's image {}", + distro.path().display(), + planned.display() + ))); + } + let (format, identity) = distro.bound(); + if plan::image_of(format, identity)? != plan.distro().image() { + return Err(refused(format!( + "the plan's image is not the {format} layer with identity {identity} that the \ + distro binding of {} names", + planned.display() + ))); + } + match (plan.upper().scratch_path(), &scratch) { + (Some(planned), Some(checked)) if !same_path(checked.path(), planned) => { + return Err(refused(format!( + "the checked scratch image {} is not the plan's scratch image {}", + checked.path().display(), + planned.display() + ))); + } + (Some(planned), None) => { + return Err(refused(format!( + "the plan's upper is on the scratch image {}, which was not checked", + planned.display() + ))); + } + (None, Some(checked)) => { + return Err(refused(format!( + "the plan's upper is in RAM, which takes no scratch image, not {}", + checked.path().display() + ))); + } + _ => {} + } + #[cfg(target_os = "linux")] + let (distro, scratch) = above_standard_streams(distro, scratch)?; + let distro_block = checked_path(distro.file(), distro.path())?; + let scratch_block = match &scratch { + Some(scratch) => Some(checked_path(scratch.file(), scratch.path())?), + None => None, + }; + let openvmm_arguments = plan.render_arguments( + &|role, _| match (role, &scratch_block) { + (BlockRole::Distro, _) => distro_block.clone(), + (BlockRole::Scratch, Some(block)) => block.clone(), + (BlockRole::Scratch, None) => { + unreachable!("a plan with a scratch block has its checked scratch image") + } + }, + cwd, + )?; + let kernel_command_line = plan.kernel_command_line(user_command_line)?; + Ok(Self { + plan, + distro, + scratch, + openvmm_arguments, + kernel_command_line, + }) + } + + /// Returns the plan, which names the images by their real paths. + pub fn plan(&self) -> &SandboxPlan { + &self.plan + } + + /// Returns the checked distro layer. + pub fn distro(&self) -> &CheckedDistro { + &self.distro + } + + /// Returns the checked scratch image, or `None` for an upper in RAM. + pub fn scratch(&self) -> Option<&CheckedScratch> { + self.scratch.as_ref() + } + + /// Returns the OpenVMM arguments of the plan, as [`SandboxPlan::openvmm_arguments`] renders + /// them, except that each block device names its checked file: on Linux as `/proc/self/fd/N`, + /// which [`inherited`](Self::inherited) passes the VMM under the number `N`, and on Windows by + /// its final path. + /// + /// They hold for this launch only: never persist them. + pub fn openvmm_arguments(&self) -> &[String] { + &self.openvmm_arguments + } + + /// Returns the kernel command line of the plan, with the user's command line given to + /// [`new`](Self::new). + pub fn kernel_command_line(&self) -> &str { + &self.kernel_command_line + } + + /// Returns the descriptors or handles that the VMM inherits, for the allowlist of its + /// [`VmmCommand::inherit`](crate::host::supervisor::VmmCommand::inherit): on Linux, those of + /// the checked distro layer and of the checked scratch image, if any, which + /// [`openvmm_arguments`](Self::openvmm_arguments) names; on Windows, the claim's handle of the + /// checked scratch image, if any, which no argument names, since the VMM opens the checked + /// files by their final paths; elsewhere none. + pub fn inherited(&self) -> Vec> { + #[cfg(target_os = "linux")] + { + let mut inherited = vec![Inherited::new(self.distro.file())]; + inherited.extend( + self.scratch + .as_ref() + .map(|scratch| Inherited::new(scratch.file())), + ); + inherited + } + #[cfg(windows)] + { + self.scratch + .iter() + .map(|scratch| Inherited::new(scratch.claim())) + .collect() + } + #[cfg(not(any(target_os = "linux", windows)))] + Vec::new() + } +} + +/// Returns whether `checked` and `planned`, two paths normalized as `pathlib.Path` normalizes +/// them, are the same text. +fn same_path(checked: &Path, planned: &Path) -> bool { + checked.as_os_str() == planned.as_os_str() +} + +/// Moves the checked files that are on a standard stream's descriptor, under whose number no spawn +/// passes them to the VMM, above the standard streams, as when the caller had closed that stream +/// before the checks opened them. A moved file keeps its open file description, and with it the +/// claim of a scratch image. +#[cfg(target_os = "linux")] +fn above_standard_streams( + mut distro: CheckedDistro, + mut scratch: Option, +) -> Result<(CheckedDistro, Option)> { + sys::move_above_standard_streams(distro.file_mut()) + .map_err(|error| os_error(&error, distro.path().to_str()))?; + if let Some(scratch) = &mut scratch { + sys::move_above_standard_streams(scratch.file_mut()) + .map_err(|error| os_error(&error, scratch.path().to_str()))?; + } + Ok((distro, scratch)) +} + +/// Returns how the OpenVMM arguments name the checked `file`, which a check opened at `path`: on +/// Linux, as `/proc/self/fd/N`; on Windows, by its final path, which resolves no link, and which +/// holds whatever the VMM's directory, refusing one that OpenVMM could not take as the path of a +/// raw image, as [`SandboxPlan::validated`] refuses the plan's paths; elsewhere, as `path`. +fn checked_path(file: &File, path: &Path) -> Result { + #[cfg(target_os = "linux")] + { + let _ = path; + Ok(Inherited::new(file).proc_path().display().to_string()) + } + #[cfg(windows)] + { + let resolved = seal::final_path(file) + .map_err(|error| os_error(&error, path.to_str()))? + .into_os_string() + .into_string() + .map_err(|_| { + refused(format!( + "the final path of {} is not valid Unicode", + path.display() + )) + })?; + // OpenVMM reads the delimiters and the extension of the path that it is given, which a + // link on the way, the directory of a relative path, or the case of a name on disk can + // make differ from the plan's. + let shown = format!("{} resolves to {resolved}", path.display()); + let pure = PurePath::native(&resolved); + reject_disk_path(&pure, &shown)?; + reject_container_path(&pure, &shown)?; + Ok(resolved) + } + #[cfg(not(any(target_os = "linux", windows)))] + { + let _ = file; + Ok(path.to_string_lossy().into_owned()) + } +} + +#[cfg(all(test, any(target_os = "linux", windows)))] +mod tests; diff --git a/nvx-rs/src/host/sandbox/prepared/tests.rs b/nvx-rs/src/host/sandbox/prepared/tests.rs new file mode 100644 index 000000000..512410929 --- /dev/null +++ b/nvx-rs/src/host/sandbox/prepared/tests.rs @@ -0,0 +1,599 @@ +//! Tests of prepared launches: the checked files that they hold, the OpenVMM arguments that name +//! them, the descriptors that the VMM inherits, and the path from staging to launch, which reads +//! no content. + +use std::fmt; +use std::fs; +use std::io::Read; +use std::path::PathBuf; +use std::time::Instant; + +use sha2::{Digest, Sha256}; + +use super::*; +use crate::ErrorKind; +use crate::host::sandbox::distro::{self, DISTRO_POLICY_REVISION, DistroBinding, PolicyVerdict}; +use crate::host::sandbox::scratch::{self, ScratchBinding}; +use crate::host::sandbox::{ + LayerRole, SandboxDistro, SandboxLaunch, SandboxLayer, SandboxSettings, SandboxUpper, +}; +use crate::host::seal::{FileSeal, open_metadata_only}; +use crate::protocol::root::RootImage; +use crate::uuid::Uuid; + +/// The UUID of the staged layers. +const UUID: &str = "11111111-1111-1111-1111-111111111111"; +/// Another UUID. +const OTHER_UUID: &str = "22222222-2222-2222-2222-222222222222"; +/// The user's kernel command line of the launches. +const USER_COMMAND_LINE: &str = "console=ttyS0"; + +/// The access of a host that lets distro bindings open files and read their metadata, but fails +/// every read and mapping of their content. +const METADATA_ONLY: distro::Access<'static> = distro::Access { + open: &open_metadata_only, + seal: &FileSeal::of, +}; + +/// The access of a host that lets scratch bindings open the image for writing alone, so that no +/// read or mapping of its content can succeed through what they open: on Linux an open for writing +/// alone, and on Windows the access of binds and checks, which never ask to read. +#[cfg(target_os = "linux")] +const WRITE_ONLY: scratch::Access<'static> = scratch::Access { + open: &open_write_only, +}; +/// The access of a host that lets scratch bindings open the image for writing alone, so that no +/// read or mapping of its content can succeed through what they open: on Linux an open for writing +/// alone, and on Windows the access of binds and checks, which never ask to read. +#[cfg(windows)] +const WRITE_ONLY: scratch::Access<'static> = scratch::Access::WRITABLE; + +/// Opens `path` for writing alone. +#[cfg(target_os = "linux")] +fn open_write_only(path: &Path) -> io::Result { + fs::OpenOptions::new().write(true).open(path) +} + +/// A distro layer with its manifest, and scratch images, staged in a temporary directory. +struct Staged { + /// The temporary directory. + directory: tempfile::TempDir, +} + +impl Staged { + /// Stages a distro layer with a manifest that binds it, another copy of the layer, and two + /// scratch images. + fn new() -> Self { + let staged = Self { + directory: tempfile::tempdir().unwrap(), + }; + let layer = b"distro layer\n"; + fs::write(staged.path("distro.erofs"), layer).unwrap(); + fs::write(staged.path("other.erofs"), layer).unwrap(); + let digest: String = Sha256::digest(layer) + .iter() + .map(|byte| format!("{byte:02x}")) + .collect(); + fs::write( + staged.path("distro.erofs.manifest.json"), + format!( + "{{\"format\": 1, \"artifact\": \"distro.erofs\", \"artifact_sha256\": \ + \"{digest}\", \"packages\": [{{\"name\": \"bash\"}}]}}" + ), + ) + .unwrap(); + fs::write(staged.path("scratch.ext4"), b"scratch image\n").unwrap(); + fs::write(staged.path("other.ext4"), b"scratch image\n").unwrap(); + staged + } + + /// Returns the path of the file `name` of the directory. + fn path(&self, name: &str) -> PathBuf { + self.directory.path().join(name) + } + + /// Returns the text of the path of the file `name`, normalized. + fn text(&self, name: &str) -> String { + PurePath::native(self.path(name).to_str().unwrap()) + .as_str() + .to_owned() + } + + /// Returns the distro layer `name`. + fn layer(&self, name: &str) -> SandboxLayer { + SandboxLayer::new(LayerRole::Distro, &self.text(name), UUID) + } + + /// Returns the launch of the layer `distro.erofs` over the image `scratch.ext4`. + fn launch(&self) -> SandboxLaunch { + SandboxLaunch::new( + vec![self.layer("distro.erofs")], + &self.text("scratch.ext4"), + SandboxSettings::default(), + ) + .unwrap() + } + + /// Verifies the layer `name`, checks it, and returns its binding and the checked layer. + fn checked_distro(&self, name: &str) -> (DistroBinding, CheckedDistro) { + let layer = self.layer(name); + let binding = DistroBinding::verify(&layer).unwrap(); + let checked = binding.check(&layer, DISTRO_POLICY_REVISION).unwrap(); + (binding, checked) + } + + /// Binds the scratch image `name`, and returns it checked and claimed. Like a launch, it waits + /// [`CLAIM_RELEASE_WAIT`](scratch::CLAIM_RELEASE_WAIT) for a claim that was released: on + /// Linux, a process that another test spawns holds a copy of the claim until it executes its + /// program. + fn checked_scratch(&self, name: &str) -> CheckedScratch { + let path = self.path(name); + ScratchBinding::bind(&path) + .unwrap() + .check(&path, Instant::now() + scratch::CLAIM_RELEASE_WAIT) + .unwrap() + } +} + +/// Returns the kind and the message of the error of `result`. +fn failure(result: Result) -> (ErrorKind, String) { + let error = result.unwrap_err(); + (error.kind(), error.to_string()) +} + +/// Returns the value of each `option` of `arguments`, in order. +fn values<'a>(arguments: &'a [String], option: &str) -> Vec<&'a str> { + arguments + .windows(2) + .filter(|pair| pair[0] == option) + .map(|pair| pair[1].as_str()) + .collect() +} + +/// Returns the number of the descriptor of `file`. +#[cfg(target_os = "linux")] +fn number(file: &fs::File) -> i32 { + use std::os::fd::AsRawFd; + + file.as_raw_fd() +} + +/// Returns the final path of the staged file `name`, by which a prepared launch names it on +/// Windows. +#[cfg(windows)] +fn final_path(staged: &Staged, name: &str) -> String { + fs::canonicalize(staged.path(name)) + .unwrap() + .to_str() + .unwrap() + .to_owned() +} + +/// Returns the value of the handle of `object`. +#[cfg(windows)] +fn raw(object: &impl std::os::windows::io::AsHandle) -> usize { + use std::os::windows::io::AsRawHandle; + + object.as_handle().as_raw_handle() as usize +} + +/// Makes `link` a junction to the directory `target`, quoting both paths, since `cmd` splits its +/// arguments at commas and semicolons. +#[cfg(windows)] +fn junction(link: &Path, target: &Path) { + use std::os::windows::process::CommandExt; + use std::process::{Command, Stdio}; + + let status = Command::new("cmd") + .raw_arg(format!( + "/C mklink /J \"{}\" \"{}\"", + link.display(), + target.display() + )) + .stdout(Stdio::null()) + .status() + .unwrap(); + assert!(status.success(), "cannot link {}", link.display()); +} + +#[test] +fn prepared_launches_hand_the_vmm_the_checked_files() { + let staged = Staged::new(); + let launch = staged.launch(); + let (binding, distro) = staged.checked_distro("distro.erofs"); + let scratch = staged.checked_scratch("scratch.ext4"); + let plan = launch.bound_plan(&binding).unwrap(); + let rendered = plan.openvmm_arguments().unwrap(); + #[cfg(target_os = "linux")] + let (distro_number, scratch_number) = (number(distro.file()), number(scratch.file())); + let prepared = + PreparedLaunch::new(plan.clone(), distro, Some(scratch), USER_COMMAND_LINE).unwrap(); + assert_eq!(prepared.plan(), &plan); + assert_eq!( + prepared.kernel_command_line(), + plan.kernel_command_line(USER_COMMAND_LINE).unwrap() + ); + assert_eq!( + prepared.distro().path().to_str().unwrap(), + staged.text("distro.erofs") + ); + assert_eq!( + prepared.scratch().unwrap().path().to_str().unwrap(), + staged.text("scratch.ext4") + ); + + // On Linux, the arguments name the checked files as descriptors that the VMM inherits, which + // reach them; on Windows, by their final paths, which the checked files hold in place. + #[cfg(target_os = "linux")] + { + use std::os::fd::{AsFd, AsRawFd}; + + let expected: Vec = rendered + .iter() + .map(|argument| { + argument + .replace( + &format!("distro:file:{},ro", staged.text("distro.erofs")), + &format!("distro:file:/proc/self/fd/{distro_number},ro"), + ) + .replace( + &format!("scratch:file:{}", staged.text("scratch.ext4")), + &format!("scratch:file:/proc/self/fd/{scratch_number}"), + ) + }) + .collect(); + assert_ne!(expected, rendered); + assert_eq!(prepared.openvmm_arguments(), expected); + let inherited: Vec = prepared + .inherited() + .iter() + .map(|inherited| inherited.as_fd().as_raw_fd()) + .collect(); + assert_eq!(inherited, [distro_number, scratch_number]); + for (number, name) in [ + (distro_number, "distro.erofs"), + (scratch_number, "scratch.ext4"), + ] { + assert_eq!( + fs::read_link(format!("/proc/self/fd/{number}")).unwrap(), + fs::canonicalize(staged.path(name)).unwrap() + ); + } + } + #[cfg(windows)] + { + let expected: Vec = rendered + .iter() + .map(|argument| { + argument + .replace( + &format!("distro:file:{},ro", staged.text("distro.erofs")), + &format!("distro:file:{},ro", final_path(&staged, "distro.erofs")), + ) + .replace( + &format!("scratch:file:{}", staged.text("scratch.ext4")), + &format!("scratch:file:{}", final_path(&staged, "scratch.ext4")), + ) + }) + .collect(); + assert_ne!(expected, rendered); + assert_eq!(prepared.openvmm_arguments(), expected); + // The VMM inherits the claim's handle of the scratch image alone, which no argument + // names, and which is not its guard. + let inherited: Vec = prepared.inherited().iter().map(raw).collect(); + let scratch = prepared.scratch().unwrap(); + assert_eq!(inherited, [raw(scratch.claim())]); + assert_ne!(raw(scratch.claim()), raw(scratch.file())); + } + + // The plan, which callers persist, keeps the real paths. + let persisted = serde_json::to_value(prepared.plan()).unwrap(); + assert_eq!(persisted["distro"]["path"], staged.text("distro.erofs")); + assert_eq!(persisted["upper"]["path"], staged.text("scratch.ext4")); +} + +#[test] +fn an_upper_in_ram_takes_no_scratch_image() { + let staged = Staged::new(); + let (_, distro) = staged.checked_distro("distro.erofs"); + #[cfg(target_os = "linux")] + let distro_number = number(distro.file()); + let plan = SandboxPlan::new( + SandboxDistro::new( + &staged.text("distro.erofs"), + RootImage::erofs(Uuid::parse(UUID)), + ), + SandboxUpper::RAM, + SandboxSettings::default(), + ) + .unwrap(); + let prepared = PreparedLaunch::new(plan.clone(), distro, None, "").unwrap(); + assert!(prepared.scratch().is_none()); + let blocks = values(prepared.openvmm_arguments(), "--microvm-sandbox-block"); + #[cfg(target_os = "linux")] + { + assert_eq!( + blocks, + [format!("distro:file:/proc/self/fd/{distro_number},ro")] + ); + assert_eq!(prepared.inherited().len(), 1); + } + #[cfg(windows)] + { + assert_eq!( + blocks, + [format!( + "distro:file:{},ro", + final_path(&staged, "distro.erofs") + )] + ); + assert!(prepared.inherited().is_empty()); + } + assert_eq!( + prepared.kernel_command_line(), + plan.kernel_command_line("").unwrap() + ); +} + +#[test] +fn prepared_launches_take_only_the_checked_files_of_their_plan() { + let staged = Staged::new(); + let launch = staged.launch(); + let distro = staged.text("distro.erofs"); + let scratch = staged.text("scratch.ext4"); + let refused = |message: String| (ErrorKind::Unsupported, message); + let plan = |binding: &DistroBinding| launch.bound_plan(binding).unwrap(); + + // Another layer than the plan's image, even with the same content and identity. + let (binding, _) = staged.checked_distro("distro.erofs"); + let (_, other) = staged.checked_distro("other.erofs"); + assert_eq!( + failure(PreparedLaunch::new( + plan(&binding), + other, + Some(staged.checked_scratch("scratch.ext4")), + "" + )), + refused(format!( + "the checked distro layer {} is not the plan's image {distro}", + staged.text("other.erofs") + )) + ); + + // A layer whose binding names another identity than the plan's image. + let (binding, checked) = staged.checked_distro("distro.erofs"); + let other_image = launch + .plan() + .with_image(RootImage::erofs(Uuid::parse(OTHER_UUID))); + assert_eq!( + failure(PreparedLaunch::new( + other_image, + checked, + Some(staged.checked_scratch("scratch.ext4")), + "" + )), + refused(format!( + "the plan's image is not the erofs layer with identity {UUID} that the distro binding \ + of {distro} names" + )) + ); + + // Another scratch image than the plan's, none for a plan on scratch, and one for an upper in + // RAM. + let (_, checked) = staged.checked_distro("distro.erofs"); + assert_eq!( + failure(PreparedLaunch::new( + plan(&binding), + checked, + Some(staged.checked_scratch("other.ext4")), + "" + )), + refused(format!( + "the checked scratch image {} is not the plan's scratch image {scratch}", + staged.text("other.ext4") + )) + ); + let (_, checked) = staged.checked_distro("distro.erofs"); + assert_eq!( + failure(PreparedLaunch::new(plan(&binding), checked, None, "")), + refused(format!( + "the plan's upper is on the scratch image {scratch}, which was not checked" + )) + ); + let (_, checked) = staged.checked_distro("distro.erofs"); + let ram = SandboxPlan::new( + SandboxDistro::new(&distro, RootImage::erofs(Uuid::parse(UUID))), + SandboxUpper::RAM, + SandboxSettings::default(), + ) + .unwrap(); + assert_eq!( + failure(PreparedLaunch::new( + ram, + checked, + Some(staged.checked_scratch("scratch.ext4")), + "" + )), + refused(format!( + "the plan's upper is in RAM, which takes no scratch image, not {scratch}" + )) + ); + + // A user's command line that the plan refuses fails the preparation. + let (_, checked) = staged.checked_distro("distro.erofs"); + assert_eq!( + failure(PreparedLaunch::new( + plan(&binding), + checked, + Some(staged.checked_scratch("scratch.ext4")), + "nvx_sandbox=0" + )), + refused("nvx_sandbox is owned by the sandbox profile".to_owned()) + ); +} + +#[cfg(windows)] +#[test] +fn prepared_launches_refuse_final_paths_that_openvmm_cannot_take() { + let staged = Staged::new(); + let refused = |message: String| (ErrorKind::Unsupported, message); + let delimited = |planned: &str, name: &str| { + refused(format!( + "disk paths containing commas or semicolons are unsupported: {} resolves to {}", + staged.text(planned), + final_path(&staged, name) + )) + }; + + // A distro layer whose final path holds a semicolon, which the plan's path hides behind a + // junction. + let layers = staged.path("real;layers"); + fs::create_dir(&layers).unwrap(); + for name in ["distro.erofs", "distro.erofs.manifest.json"] { + fs::copy(staged.path(name), layers.join(name)).unwrap(); + } + junction(&staged.path("layers"), &layers); + let layer = SandboxLayer::new( + LayerRole::Distro, + &staged.text("layers\\distro.erofs"), + UUID, + ); + let binding = DistroBinding::verify(&layer).unwrap(); + let checked = binding.check(&layer, DISTRO_POLICY_REVISION).unwrap(); + let launch = SandboxLaunch::new( + vec![layer], + &staged.text("scratch.ext4"), + SandboxSettings::default(), + ) + .unwrap(); + assert_eq!( + failure(PreparedLaunch::new( + launch.bound_plan(&binding).unwrap(), + checked, + Some(staged.checked_scratch("scratch.ext4")), + "" + )), + delimited("layers\\distro.erofs", "real;layers\\distro.erofs") + ); + + // A scratch image whose final path holds a comma, behind a junction too. + let images = staged.path("real,images"); + fs::create_dir(&images).unwrap(); + fs::copy(staged.path("scratch.ext4"), images.join("scratch.ext4")).unwrap(); + junction(&staged.path("images"), &images); + let plan_on = |scratch: &str| { + let (binding, checked) = staged.checked_distro("distro.erofs"); + let launch = SandboxLaunch::new( + vec![staged.layer("distro.erofs")], + &staged.text(scratch), + SandboxSettings::default(), + ) + .unwrap(); + (launch.bound_plan(&binding).unwrap(), checked) + }; + let (plan, checked) = plan_on("images\\scratch.ext4"); + assert_eq!( + failure(PreparedLaunch::new( + plan, + checked, + Some(staged.checked_scratch("images\\scratch.ext4")), + "" + )), + delimited("images\\scratch.ext4", "real,images\\scratch.ext4") + ); + + // A scratch image whose name on disk ends in `.vhd`, which the plan's path names in capitals: + // OpenVMM, comparing extensions case-sensitively, would attach the plan's path as a raw + // image, but would open the final path as a VHD. + fs::write(staged.path("image.vhd"), b"scratch image\n").unwrap(); + let (plan, checked) = plan_on("IMAGE.VHD"); + assert_eq!( + failure(PreparedLaunch::new( + plan, + checked, + Some(staged.checked_scratch("IMAGE.VHD")), + "" + )), + refused(format!( + "disk paths ending in .vhd are unsupported, since OpenVMM would not attach them as \ + raw images: {} resolves to {}", + staged.text("IMAGE.VHD"), + final_path(&staged, "image.vhd") + )) + ); +} + +#[test] +fn staging_checking_and_preparing_a_launch_read_no_content() { + let staged = Staged::new(); + let launch = staged.launch(); + let scratch = PurePath::native(&staged.text("scratch.ext4")); + + // Staging: a caller that verified the layer imports its binding, with its manifest, and binds + // the scratch image. Then the checks and the preparation of the launch. On this host, every + // read and mapping of content through what they open fails. + let binding = + distro::import_with(launch.distro(), PolicyVerdict::ACCEPTED, METADATA_ONLY).unwrap(); + assert!(binding.manifest().is_some()); + let scratch_binding = scratch::bind_with(&scratch, WRITE_ONLY).unwrap(); + let checked = binding + .check_with(launch.distro(), DISTRO_POLICY_REVISION, METADATA_ONLY) + .unwrap(); + let checked_scratch = scratch_binding + .check_with(&scratch, Instant::now(), WRITE_ONLY) + .unwrap(); + let prepared = PreparedLaunch::new( + launch.bound_plan(&binding).unwrap(), + checked, + Some(checked_scratch), + USER_COMMAND_LINE, + ) + .unwrap(); + let mut byte = [0]; + assert!(prepared.distro().file().read(&mut byte).is_err()); + assert!(prepared.scratch().unwrap().file().read(&mut byte).is_err()); + + // Verification, the positive control, reads content: it fails on that host... + let (kind, message) = failure(distro::verify_with(launch.distro(), METADATA_ONLY)); + assert_eq!(kind, ErrorKind::Unsupported); + assert!( + message.starts_with("invalid sandbox distro manifest: ["), + "{message}" + ); + // ...and with reads allowed, binds the layer as the staging caller did. + assert_eq!(DistroBinding::verify(launch.distro()).unwrap(), binding); +} + +#[test] +fn no_plan_encodes_root_as_the_workload_identity() { + // The VMM receives the plan's identity, which is never root's: a root profile is the + // adapter's choice, which it makes otherwise. + let staged = Staged::new(); + for identity in [(0, 0), (0, 1000), (1000, 0)] { + let settings = SandboxSettings { + workload_identity: identity, + ..SandboxSettings::default() + }; + let (kind, message) = failure(SandboxPlan::new( + SandboxDistro::new( + &staged.text("distro.erofs"), + RootImage::erofs(Uuid::parse(UUID)), + ), + SandboxUpper::RAM, + settings, + )); + assert_eq!(kind, ErrorKind::Unsupported, "{message}"); + } + let (_, distro) = staged.checked_distro("distro.erofs"); + let prepared = PreparedLaunch::new( + staged.launch().plan().clone(), + distro, + Some(staged.checked_scratch("scratch.ext4")), + "", + ) + .unwrap(); + assert_eq!( + values(prepared.openvmm_arguments(), "--microvm-workload-identity"), + ["65534:65534"] + ); +} diff --git a/nvx-rs/src/host/sandbox/scratch.rs b/nvx-rs/src/host/sandbox/scratch.rs new file mode 100644 index 000000000..33aa21ad2 --- /dev/null +++ b/nvx-rs/src/host/sandbox/scratch.rs @@ -0,0 +1,526 @@ +//! The binding of a sandbox's scratch image: what identifies the image, recorded so that a launch +//! checks it from its metadata alone, and claims it for one sandbox. +//! +//! The scratch image holds the writable upper of a layered root, a whole-device ext4 filesystem +//! that the VMM writes. Unlike a distro layer's, its content and times change while a sandbox +//! runs, so a [`ScratchBinding`] records neither, only what tells a replacement apart and what +//! the launch relies on: the platform scheme, the device or volume and the file ID of the image, +//! its kind, a regular file, and its length, which fixes the size of the guest's block device. +//! +//! [`ScratchBinding::bind`] records the image where a caller stages or provisions it, and +//! [`ScratchBinding::check`] checks it before a launch. Neither reads or maps the image's content. +//! The check opens the image, compares it with the binding, claims it, compares it again, since +//! the launch whose claim it waited for may have changed it, and returns it open, as a +//! [`CheckedScratch`], which a [`PreparedLaunch`](super::PreparedLaunch) hands to the VMM: a +//! replacement, or a change of kind or length, fails the check. On Linux, the file ID is the inode +//! number, which a file that is created once the image is removed can take again: a replacement +//! made that way, with the image's length, passes the check. +//! +//! # Claims +//! +//! A scratch image belongs to one sandbox at a time: a check claims it for its launch, and the +//! claim lasts as long as that launch's VMM holds the image, so a second sandbox cannot claim it +//! meanwhile. A check that finds the image claimed waits until its deadline for the claim to be +//! released, then fails with [`ErrorKind::TimedOut`](crate::ErrorKind::TimedOut). +//! +//! - On Linux, the check opens the image for reading and writing and takes an exclusive `flock` +//! lock of that open file description. The VMM inherits the descriptor, which it opens again as +//! `/proc/self/fd/N`, and keeps it until it exits, so the claim lasts until both the caller and +//! the VMM have closed it. A process that another thread of the caller spawns while the +//! descriptor is open holds a copy of it from its fork until it executes its program, and with +//! it the claim, for that moment: a check therefore waits for a claim that should be free, for +//! [`CLAIM_RELEASE_WAIT`] for example, rather than fail at once. +//! - On Windows, the check's handle is a guard that holds no access to the image's content: it +//! shares reading and writing, which OpenVMM's open of the image needs, and denies deleting and +//! renaming it, so that the image's path names the checked image until the guard is released. +//! Two guards exclude each other, since each asks for the delete access that the other denies. +//! The check then makes sure that no other process has the image open for writing, as the VMM +//! of another sandbox does: an open that denies writing succeeds only then. It then opens the +//! image for writing, sharing every access, as the claim's handle, which the VMM inherits and +//! never uses: so the VMM holds the claim from its start, even if the caller ends before the VMM +//! has opened the image, which would release the guard. Once the VMM has opened the image, at +//! authenticated Ready, the caller releases the guard, and the VMM's handles, open for writing, +//! keep the claim. +//! +//! Claims exclude the launches of NVX: a process that writes the image without claiming it, which +//! on Linux takes no `flock` lock and on Windows does not keep the image open, is not kept out. +//! As for a distro layer, callers stage scratch images in a directory that only their own account +//! can write. + +use std::fs::File; +use std::io; +use std::path::Path; +use std::thread; +use std::time::{Duration, Instant}; + +use serde::{Deserialize, Serialize}; + +use super::mount::os_error; +use super::refused; +use crate::error::{Error, Result}; +use crate::host::python::path::PurePath; +use crate::host::seal::{FileId, FileKind, FileSeal, SealScheme}; + +/// The version of the serde form of [`ScratchBinding`] that this build writes and checks. +pub const SCRATCH_BINDING_VERSION: u32 = 1; + +/// How long a check of a scratch image that should be free waits for its claim to be released: +/// long enough for a process that another thread of the caller spawned to execute its program, +/// and so to close its copy of a claim, as [Claims](self#claims) explains. +pub const CLAIM_RELEASE_WAIT: Duration = Duration::from_secs(1); + +/// How often a check tries a claim that is held again. +const CLAIM_RETRY_INTERVAL: Duration = Duration::from_millis(25); + +/// How messages name a scratch image. +const DESCRIPTION: &str = "ext4 scratch image"; + +/// How the operations of a binding open the scratch image: for reading and writing, as this +/// module opens it, or, in tests, for writing alone, so that no read or mapping of its content +/// can succeed. +#[derive(Clone, Copy)] +pub(crate) struct Access<'a> { + /// Opens the image for writing, to bind it, and on hosts other than Windows to check it. + pub(crate) open: &'a dyn Fn(&Path) -> io::Result, +} + +impl Access<'static> { + /// The access of binds and checks: [`imp::open_writable`]. + pub(crate) const WRITABLE: Self = Self { + open: &imp::open_writable, + }; +} + +/// The binding of a scratch image: its platform scheme, device or volume, file ID, kind, and +/// length. +/// +/// [`bind`](Self::bind) makes a binding, and [`check`](Self::check) checks the image against it, +/// and claims it, before a launch. Its serde form, which callers record, holds every field, each +/// required: +/// +/// ```json +/// {"version": 1, "scheme": "linux", "device": 2049, "fileId": "0000000000000000000000000004a2f1", +/// "kind": "regular", "length": 67108864} +/// ``` +/// +/// with the fields of a [`FileSeal`] of the same names, and no times, which the VMM changes. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct ScratchBinding { + /// [`SCRATCH_BINDING_VERSION`]. + version: u32, + /// The host that took the binding. + scheme: SealScheme, + /// `st_dev`, or the volume serial number on Windows. + device: u64, + /// `st_ino`, or the file ID on Windows. + file_id: FileId, + /// The kind of file: a regular file. + kind: FileKind, + /// The length in bytes. + length: u64, +} + +/// A scratch image that [`ScratchBinding::check`] found unchanged and claimed, held open. +/// +/// On Linux, the file is the image, open for reading and writing, whose open file description +/// holds the claim. On Windows, it is the image's guard, which holds no access to its content and +/// denies deleting and renaming it, so that the image's path names the checked image while the +/// guard is held, and the checked scratch also holds the claim's handle, open for writing, which +/// the VMM inherits. Dropping it closes them, which releases the claim unless a VMM that inherited +/// it still holds it; see [Claims](self#claims). +/// +/// A [`PreparedLaunch`](super::PreparedLaunch) takes it, with the path at which the check opened +/// the image, and hands the image to the VMM. +#[derive(Debug)] +pub struct CheckedScratch { + /// The checked image, or on Windows its guard. + file: File, + /// The claim's handle on Windows: the image, open for writing and sharing every access, + /// which the VMM inherits so that it holds the claim from its start. + #[cfg(windows)] + claim: File, + /// The path at which the check opened the image. + path: PurePath, +} + +impl CheckedScratch { + /// Returns the checked image: on Linux, the image open for reading and writing, and on Windows + /// its guard. + pub fn file(&self) -> &File { + &self.file + } + + /// Returns the path at which the check opened the image. + pub fn path(&self) -> &Path { + self.path.as_path() + } + + /// Returns the checked image, which the caller then holds with its claim: an owned descriptor + /// on Linux, and on Windows an owned handle, the image's guard, which then holds the claim + /// alone, since this closes the claim's handle. + pub fn into_file(self) -> File { + self.file + } + + /// Returns the claim's handle, which the VMM inherits on Windows. + #[cfg(windows)] + pub(crate) fn claim(&self) -> &File { + &self.claim + } + + /// Returns the checked image, for a prepared launch to move. + #[cfg(target_os = "linux")] + pub(crate) fn file_mut(&mut self) -> &mut File { + &mut self.file + } +} + +impl ScratchBinding { + /// Binds the scratch image at `path`, which is normalized as `pathlib.Path` normalizes it, + /// without reading it, and returns its binding. + /// + /// The image must be a regular file that this process may open for writing, as the VMM + /// opens it. Binding claims nothing. Fails with + /// [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) for a path that is not Unicode, + /// with [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) for an image that is missing + /// or not a regular file, and with [`ErrorKind::Io`](crate::ErrorKind::Io) if the image cannot + /// be opened or sealed, as on hosts other than Linux and Windows. + pub fn bind(path: &Path) -> Result { + bind_with(&pure(path)?, Access::WRITABLE) + } + + /// Checks the scratch image at `path`, normalized as `pathlib.Path` normalizes it, against this + /// binding from its metadata alone, claims it, and returns it open, as the check found it. + /// + /// The binding must be of [`SCRATCH_BINDING_VERSION`], and the image must have the binding's + /// scheme, identity, kind, and length. The check then claims the image, as + /// [Claims](self#claims) describes, waiting until `deadline` for a claim that another launch + /// holds to be released, and compares the image with the binding again once it holds the + /// claim. It never reads or maps the image's content. + /// + /// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) for a path that is not + /// Unicode; with [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) for a binding of + /// another version, a missing image, and an image that changed; with + /// [`ErrorKind::TimedOut`](crate::ErrorKind::TimedOut) for an image that another launch still + /// claims at `deadline`; and with [`ErrorKind::Io`](crate::ErrorKind::Io) if the image cannot + /// be opened, sealed, or claimed. + pub fn check(&self, path: &Path, deadline: Instant) -> Result { + self.check_with(&pure(path)?, deadline, Access::WRITABLE) + } + + /// Returns the host that took the binding. + pub fn scheme(&self) -> SealScheme { + self.scheme + } + + /// Returns the device, or on Windows the volume serial number. + pub fn device(&self) -> u64 { + self.device + } + + /// Returns the inode, or on Windows the file ID. + pub fn file_id(&self) -> u128 { + self.file_id.0 + } + + /// Returns the kind of file. + pub fn kind(&self) -> FileKind { + self.kind + } + + /// Returns the length in bytes. + pub fn length(&self) -> u64 { + self.length + } + + /// Returns the binding of the image that `seal` seals. + fn of(seal: &FileSeal) -> Self { + Self { + version: SCRATCH_BINDING_VERSION, + scheme: seal.scheme(), + device: seal.device(), + file_id: FileId(seal.file_id()), + kind: seal.kind(), + length: seal.length(), + } + } + + /// Returns what differs first between the image that this binding records and the one that + /// `seal` seals, in the order scheme, identity, kind, and length, or `None` if nothing does. + fn difference(&self, seal: &FileSeal) -> Option<&'static str> { + if self.scheme != seal.scheme() { + Some("seal scheme") + } else if (self.device, self.file_id.0) != (seal.device(), seal.file_id()) { + Some("identity") + } else if self.kind != seal.kind() { + Some("kind") + } else if self.length != seal.length() { + Some("length") + } else { + None + } + } + + /// Implements [`check`](Self::check), opening the image through `access`. + pub(crate) fn check_with( + &self, + path: &PurePath, + deadline: Instant, + access: Access<'_>, + ) -> Result { + if self.version != SCRATCH_BINDING_VERSION { + return Err(Error::unavailable(format!( + "the scratch binding of {path} has version {}, which this build does not check", + self.version + ))); + } + #[cfg(windows)] + let (file, claim) = imp::open_checked(self, path, deadline, access)?; + #[cfg(not(windows))] + let file = imp::open_checked(self, path, deadline, access)?; + Ok(CheckedScratch { + file, + #[cfg(windows)] + claim, + path: path.clone(), + }) + } + + /// Fails if the image that `seal` seals, which the check opened at `path`, is not the one + /// that this binding records. + fn compare(&self, seal: &FileSeal, path: &PurePath) -> Result<()> { + match self.difference(seal) { + Some(field) => Err(Error::unavailable(format!( + "{path} changed after its scratch binding was made: its {field} differs" + ))), + None => Ok(()), + } + } +} + +/// Implements [`ScratchBinding::bind`], opening the image through `access`. +pub(crate) fn bind_with(path: &PurePath, access: Access<'_>) -> Result { + let file = match (access.open)(path.as_path()) { + Ok(file) => file, + Err(error) if error.kind() == io::ErrorKind::IsADirectory => return Err(missing(path)), + Err(error) => return Err(open_error(&error, path)), + }; + let seal = FileSeal::of(&file).map_err(|error| os_error(&error, Some(path.as_str())))?; + if seal.kind() != FileKind::Regular { + return Err(missing(path)); + } + Ok(ScratchBinding::of(&seal)) +} + +/// Returns `path`, normalized as `pathlib.Path` normalizes it. +/// +/// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) for a path that is not +/// Unicode, which no plan names. +fn pure(path: &Path) -> Result { + path.to_str().map(PurePath::native).ok_or_else(|| { + refused(format!( + "{DESCRIPTION} path is not valid Unicode: {}", + path.display() + )) + }) +} + +/// Returns the error of a scratch image that is missing, or that is not a regular file, as +/// `require_file` refuses its path. +fn missing(path: &PurePath) -> Error { + Error::unavailable(format!("{DESCRIPTION} not found: {path}")) +} + +/// Returns the error of a scratch image that cannot be opened: unavailable if it is missing. +fn open_error(error: &io::Error, path: &PurePath) -> Error { + if error.kind() == io::ErrorKind::NotFound { + missing(path) + } else { + os_error(error, Some(path.as_str())) + } +} + +/// Returns the error of a scratch image that another launch still claims. +fn claimed(path: &PurePath) -> Error { + Error::timed_out(format!("another sandbox claims the {DESCRIPTION} {path}")) +} + +/// Runs `attempt` until it returns a value, fails, or `deadline` passes, at every +/// [`CLAIM_RETRY_INTERVAL`]: `attempt` returns `None` while another launch claims the image at +/// `path`. +fn until_unclaimed( + path: &PurePath, + deadline: Instant, + mut attempt: impl FnMut() -> Result>, +) -> Result { + loop { + if let Some(value) = attempt()? { + return Ok(value); + } + let left = deadline.saturating_duration_since(Instant::now()); + if left.is_zero() { + return Err(claimed(path)); + } + thread::sleep(left.min(CLAIM_RETRY_INTERVAL)); + } +} + +#[cfg(not(windows))] +/// The opens and claims of hosts other than Windows: Linux, and hosts that cannot seal files, on +/// which every bind and check fails. +mod imp { + use std::fs::{File, OpenOptions, TryLockError}; + use std::io; + use std::path::Path; + use std::time::Instant; + + use super::{Access, PurePath, ScratchBinding, open_error, os_error, until_unclaimed}; + use crate::error::Result; + use crate::host::seal::{self, FileSeal}; + + /// Opens `path` for reading and writing, following links, and on Unix without waiting for a + /// writer of a FIFO, which the seal then shows. + pub(super) fn open_writable(path: &Path) -> io::Result { + let mut options = OpenOptions::new(); + options.read(true).write(true); + #[cfg(unix)] + std::os::unix::fs::OpenOptionsExt::custom_flags( + &mut options, + nix::fcntl::OFlag::O_NONBLOCK.bits(), + ); + options.open(path) + } + + /// Opens the image at `path` through `access`, compares it with `binding`, and claims it with + /// an exclusive `flock` lock of the open file description, waiting until `deadline` for + /// another launch to release its claim, then compares it again. + pub(super) fn open_checked( + binding: &ScratchBinding, + path: &PurePath, + deadline: Instant, + access: Access<'_>, + ) -> Result { + let sealed = + |file: &File| FileSeal::of(file).map_err(|error| os_error(&error, Some(path.as_str()))); + let file = match (access.open)(path.as_path()) { + Ok(file) => file, + // A directory took the image's place: its seal tells how it differs. + Err(error) if error.kind() == io::ErrorKind::IsADirectory => { + let directory = + seal::open_read(path.as_path()).map_err(|error| open_error(&error, path))?; + binding.compare(&sealed(&directory)?, path)?; + return Err(open_error(&error, path)); + } + Err(error) => return Err(open_error(&error, path)), + }; + binding.compare(&sealed(&file)?, path)?; + until_unclaimed(path, deadline, || match file.try_lock() { + Ok(()) => Ok(Some(())), + Err(TryLockError::WouldBlock) => Ok(None), + Err(TryLockError::Error(error)) => Err(os_error(&error, Some(path.as_str()))), + })?; + // The launch whose claim the check waited for may have changed the image meanwhile. + binding.compare(&sealed(&file)?, path)?; + Ok(file) + } +} + +#[cfg(windows)] +/// The opens and claims of Windows, through share modes. +mod imp { + use std::fs::{File, OpenOptions}; + use std::io; + use std::os::windows::fs::OpenOptionsExt; + use std::path::Path; + use std::time::Instant; + + use windows_sys::Win32::Foundation::ERROR_SHARING_VIOLATION; + use windows_sys::Win32::Storage::FileSystem::{ + DELETE, FILE_FLAG_BACKUP_SEMANTICS, FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE, + FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_WRITE_DATA, SYNCHRONIZE, + }; + + use super::{Access, PurePath, ScratchBinding, open_error, os_error, until_unclaimed}; + use crate::error::Result; + use crate::host::seal::FileSeal; + + /// Opens `path` for writing and for its attributes, but not for reading, following links, + /// sharing every access, as a bind does, and as a check opens the claim's handle. + pub(super) fn open_writable(path: &Path) -> io::Result { + OpenOptions::new() + .access_mode(FILE_READ_ATTRIBUTES | FILE_WRITE_DATA | SYNCHRONIZE) + .share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE) + .custom_flags(FILE_FLAG_BACKUP_SEMANTICS) + .open(path) + } + + /// Opens the guard of the image at `path`: for its attributes and for deleting it, which no + /// other guard shares, sharing reading and writing, and denying deleting and renaming the + /// image while it is held. + fn open_guard(path: &Path) -> io::Result { + OpenOptions::new() + .access_mode(FILE_READ_ATTRIBUTES | DELETE | SYNCHRONIZE) + .share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE) + .custom_flags(FILE_FLAG_BACKUP_SEMANTICS) + .open(path) + } + + /// Opens the image at `path` for writing, denying writing, which succeeds only while no other + /// process has the image open for writing. + fn open_sole_writer(path: &Path) -> io::Result { + OpenOptions::new() + .access_mode(FILE_READ_ATTRIBUTES | FILE_WRITE_DATA | SYNCHRONIZE) + .share_mode(FILE_SHARE_READ | FILE_SHARE_DELETE) + .custom_flags(FILE_FLAG_BACKUP_SEMANTICS) + .open(path) + } + + /// Returns whether `error` is a sharing violation, which an open whose share mode or access + /// conflicts with another handle of the file fails with. + fn sharing_violation(error: &io::Error) -> bool { + error.raw_os_error() == Some(ERROR_SHARING_VIOLATION as i32) + } + + /// Opens the guard of the image at `path`, compares the image with `binding`, and claims it, + /// as [Claims](super#claims) describes, waiting until `deadline` for another launch to release + /// its claim, then compares it again, and returns the guard and the claim's handle. The image + /// is opened for its attributes, and for writing or deleting it, but never for reading, so + /// `access`, which binds use, does not apply. + pub(super) fn open_checked( + binding: &ScratchBinding, + path: &PurePath, + deadline: Instant, + _access: Access<'_>, + ) -> Result<(File, File)> { + let sealed = + |file: &File| FileSeal::of(file).map_err(|error| os_error(&error, Some(path.as_str()))); + let guard = until_unclaimed(path, deadline, || match open_guard(path.as_path()) { + Ok(guard) => Ok(Some(guard)), + Err(error) if sharing_violation(&error) => Ok(None), + Err(error) => Err(open_error(&error, path)), + })?; + binding.compare(&sealed(&guard)?, path)?; + until_unclaimed(path, deadline, || match open_sole_writer(path.as_path()) { + Ok(writer) => { + // The guard keeps the path naming the image, so this is the guarded image, which + // the writer that the check waited for may have changed meanwhile. The probe keeps + // any other writer out while the check compares it again. + binding.compare(&sealed(&writer)?, path)?; + Ok(Some(())) + } + Err(error) if sharing_violation(&error) => Ok(None), + Err(error) => Err(open_error(&error, path)), + })?; + // The probe is closed, and no other check probes the image while this one holds the guard. + // The claim's handle, open for writing, then keeps the probes of other checks out for as + // long as this process, or a VMM that inherits it, holds it. A link on the way that now + // leads elsewhere fails the comparison. + let claim = open_writable(path.as_path()).map_err(|error| open_error(&error, path))?; + binding.compare(&sealed(&claim)?, path)?; + Ok((guard, claim)) + } +} + +#[cfg(all(test, any(target_os = "linux", windows)))] +mod tests; diff --git a/nvx-rs/src/host/sandbox/scratch/tests.rs b/nvx-rs/src/host/sandbox/scratch/tests.rs new file mode 100644 index 000000000..374cc9ddb --- /dev/null +++ b/nvx-rs/src/host/sandbox/scratch/tests.rs @@ -0,0 +1,518 @@ +//! Tests of scratch bindings: binds and checks, which read no content, and the claims of checks. + +use std::fmt; +use std::fs::{self, OpenOptions}; +use std::io::{Read, Write}; +use std::path::PathBuf; +use std::time::SystemTime; + +use super::*; +use crate::ErrorKind; + +/// The content of the staged scratch images. +const CONTENT: &[u8] = b"scratch image\n"; + +/// The access of a host that lets binds and checks open the image for writing alone, so that no +/// read or mapping of its content can succeed through what they open: on Linux an open for writing +/// alone, and on Windows the access of binds and checks, which never ask to read. +#[cfg(target_os = "linux")] +const WRITE_ONLY: Access<'static> = Access { + open: &open_write_only, +}; +/// The access of a host that lets binds and checks open the image for writing alone, so that no +/// read or mapping of its content can succeed through what they open: on Linux an open for writing +/// alone, and on Windows the access of binds and checks, which never ask to read. +#[cfg(windows)] +const WRITE_ONLY: Access<'static> = Access::WRITABLE; + +/// Opens `path` for writing alone, without waiting for a reader of a FIFO. +#[cfg(target_os = "linux")] +fn open_write_only(path: &Path) -> io::Result { + use std::os::unix::fs::OpenOptionsExt; + + OpenOptions::new() + .write(true) + .custom_flags(nix::fcntl::OFlag::O_NONBLOCK.bits()) + .open(path) +} + +/// A scratch image staged in a temporary directory. +struct Staged { + /// The temporary directory. + directory: tempfile::TempDir, +} + +impl Staged { + /// Stages a scratch image. + fn new() -> Self { + let staged = Self { + directory: tempfile::tempdir().unwrap(), + }; + fs::write(staged.image(), CONTENT).unwrap(); + staged + } + + /// Returns the path of the image. + fn image(&self) -> PathBuf { + self.directory.path().join("scratch.ext4") + } + + /// Returns the path of another file of the directory. + fn other(&self, name: &str) -> PathBuf { + self.directory.path().join(name) + } + + /// Returns the path of the image, normalized. + fn pure(&self) -> PurePath { + PurePath::native(self.image().to_str().unwrap()) + } + + /// Binds the image through [`WRITE_ONLY`]. + fn bind(&self) -> ScratchBinding { + bind_with(&self.pure(), WRITE_ONLY).unwrap() + } + + /// Checks the image against `binding` through [`WRITE_ONLY`], until `deadline`. + fn check(&self, binding: &ScratchBinding, deadline: Instant) -> Result { + binding.check_with(&self.pure(), deadline, WRITE_ONLY) + } + + /// Checks the image against `binding` through [`WRITE_ONLY`], and returns it claimed. Like a + /// launch, it waits [`CLAIM_RELEASE_WAIT`] for a claim that was released: on Linux, a process + /// that another test spawns holds a copy of the claim until it executes its program. + fn claim(&self, binding: &ScratchBinding) -> CheckedScratch { + self.check(binding, Instant::now() + CLAIM_RELEASE_WAIT) + .unwrap() + } +} + +/// Returns `path` as messages show it. +fn shown(path: &Path) -> String { + PurePath::native(path.to_str().unwrap()).as_str().to_owned() +} + +/// Returns the kind and the message of the error of `result`. +fn failure(result: Result) -> (ErrorKind, String) { + let error = result.unwrap_err(); + (error.kind(), error.to_string()) +} + +/// Returns the failure of a check of the image at `path` that another sandbox claims. +fn claimed_failure(path: &Path) -> (ErrorKind, String) { + ( + ErrorKind::TimedOut, + format!( + "another sandbox claims the ext4 scratch image {}", + shown(path) + ), + ) +} + +/// Returns the failure of a check of the image at `path`, whose `field` changed. +fn changed_failure(path: &Path, field: &str) -> (ErrorKind, String) { + ( + ErrorKind::Unavailable, + format!( + "{} changed after its scratch binding was made: its {field} differs", + shown(path) + ), + ) +} + +/// Returns whether processes that this one launches inherit `file`. +#[cfg(target_os = "linux")] +fn inheritable(file: &File) -> bool { + use nix::fcntl::{FcntlArg, FdFlag, fcntl}; + + let flags = fcntl(file, FcntlArg::F_GETFD).unwrap(); + !FdFlag::from_bits_truncate(flags).contains(FdFlag::FD_CLOEXEC) +} + +/// Returns whether processes that this one launches inherit `file`. +#[cfg(windows)] +#[allow(unsafe_code)] +fn inheritable(file: &File) -> bool { + use std::os::windows::io::AsRawHandle; + + use windows_sys::Win32::Foundation::{GetHandleInformation, HANDLE, HANDLE_FLAG_INHERIT}; + + let mut flags = 0; + // SAFETY: the handle is open for as long as `file` is borrowed, and the output pointer is + // valid. + assert_ne!( + unsafe { GetHandleInformation(file.as_raw_handle() as HANDLE, &mut flags) }, + 0 + ); + flags & HANDLE_FLAG_INHERIT != 0 +} + +#[test] +fn binds_record_the_image_without_reading_it() { + let staged = Staged::new(); + let binding = staged.bind(); + let seal = FileSeal::of(&File::open(staged.image()).unwrap()).unwrap(); + assert_eq!(binding.scheme(), seal.scheme()); + assert_eq!( + (binding.device(), binding.file_id()), + (seal.device(), seal.file_id()) + ); + assert_eq!(binding.kind(), FileKind::Regular); + assert_eq!(binding.length(), CONTENT.len() as u64); + assert_eq!(ScratchBinding::bind(&staged.image()).unwrap(), binding); + // The positive control: what the access of the test opens refuses every read. + let mut byte = [0]; + let mut opened = (WRITE_ONLY.open)(&staged.image()).unwrap(); + assert!(opened.read(&mut byte).is_err()); +} + +#[test] +fn bindings_round_trip_through_their_serde_form() { + let staged = Staged::new(); + let binding = staged.bind(); + let value = serde_json::to_value(&binding).unwrap(); + let mut keys: Vec = value.as_object().unwrap().keys().cloned().collect(); + keys.sort_unstable(); + assert_eq!( + keys, + ["device", "fileId", "kind", "length", "scheme", "version"] + ); + assert_eq!(value["version"], SCRATCH_BINDING_VERSION); + assert_eq!( + value["scheme"], + if cfg!(windows) { "windows" } else { "linux" } + ); + assert_eq!(value["fileId"], format!("{:032x}", binding.file_id())); + assert_eq!(value["kind"], "regular"); + assert_eq!(value["length"], CONTENT.len()); + assert_eq!( + serde_json::from_value::(value.clone()).unwrap(), + binding + ); + // Every field is required, and no other is accepted, such as a time, which the VMM changes. + for key in &keys { + let mut partial = value.clone(); + partial.as_object_mut().unwrap().remove(key); + assert!( + serde_json::from_value::(partial).is_err(), + "{key}" + ); + } + let mut extended = value.clone(); + extended["modified"] = serde_json::json!({"seconds": 0, "nanoseconds": 0}); + assert!(serde_json::from_value::(extended).is_err()); +} + +#[test] +fn binds_refuse_what_is_not_a_scratch_image() { + let staged = Staged::new(); + let not_found = |path: &Path| { + ( + ErrorKind::Unavailable, + format!("ext4 scratch image not found: {}", shown(path)), + ) + }; + let missing = staged.other("missing.ext4"); + assert_eq!(failure(ScratchBinding::bind(&missing)), not_found(&missing)); + let directory = staged.other("directory.ext4"); + fs::create_dir(&directory).unwrap(); + assert_eq!( + failure(ScratchBinding::bind(&directory)), + not_found(&directory) + ); + // No plan names a path that is not Unicode. + #[cfg(target_os = "linux")] + { + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + + let path = staged + .other("") + .join(OsStr::from_bytes(b"scratch-\xff.ext4")); + for (kind, message) in [ + failure(ScratchBinding::bind(&path)), + failure(staged.bind().check(&path, Instant::now())), + ] { + assert_eq!(kind, ErrorKind::Unsupported); + assert!( + message.starts_with("ext4 scratch image path is not valid Unicode: "), + "{message}" + ); + } + } +} + +#[test] +fn checks_claim_the_image_without_reading_it() { + let staged = Staged::new(); + let binding = staged.bind(); + let checked = staged.check(&binding, Instant::now()).unwrap(); + assert_eq!(checked.path().to_str().unwrap(), shown(&staged.image())); + let seal = FileSeal::of(checked.file()).unwrap(); + assert_eq!( + (seal.device(), seal.file_id()), + (binding.device(), binding.file_id()) + ); + // What the check holds reads nothing, and processes that this one launches do not inherit + // it. + let mut byte = [0]; + assert!(checked.file().read(&mut byte).is_err()); + assert!(!inheritable(checked.file())); + #[cfg(windows)] + { + assert!(checked.claim().read(&mut byte).is_err()); + assert!(!inheritable(checked.claim())); + } + + // A second check finds the image claimed until its deadline... + assert_eq!( + failure(staged.check(&binding, Instant::now())), + claimed_failure(&staged.image()) + ); + // ...and claims it once the first check's claim is released before then. + let started = Instant::now(); + let release = Duration::from_millis(200); + let releaser = std::thread::spawn(move || { + std::thread::sleep(release); + drop(checked); + }); + let second = staged + .check(&binding, started + Duration::from_secs(30)) + .unwrap(); + assert!(started.elapsed() >= release); + releaser.join().unwrap(); + assert_eq!( + failure(staged.check(&binding, Instant::now())), + claimed_failure(&staged.image()) + ); + drop(second); + // The access of launches claims as the test's does. + let checked = binding + .check(&staged.image(), Instant::now() + CLAIM_RELEASE_WAIT) + .unwrap(); + assert_eq!( + failure(staged.check(&binding, Instant::now())), + claimed_failure(&staged.image()) + ); + drop(checked); + drop(staged.claim(&binding)); +} + +#[test] +fn a_claim_lasts_while_the_vmm_holds_the_image() { + let staged = Staged::new(); + let binding = staged.bind(); + let checked = binding + .check(&staged.image(), Instant::now() + CLAIM_RELEASE_WAIT) + .unwrap(); + // The VMM's hold of the image: on Linux the descriptor that it inherits, a copy of the + // checked one, and on Windows its own open for reading and writing, which the guard shares. + #[cfg(target_os = "linux")] + let mut vmm = checked.file().try_clone().unwrap(); + #[cfg(windows)] + let mut vmm = OpenOptions::new() + .read(true) + .write(true) + .open(staged.image()) + .unwrap(); + vmm.write_all(b"S").unwrap(); + // The caller releases what it holds once the VMM is ready: the VMM keeps the claim. + drop(checked); + assert_eq!( + failure(staged.check(&binding, Instant::now())), + claimed_failure(&staged.image()) + ); + drop(vmm); + drop(staged.claim(&binding)); +} + +#[cfg(windows)] +#[test] +fn the_claims_handle_keeps_the_claim_once_the_guard_is_closed() { + let staged = Staged::new(); + let binding = staged.bind(); + let CheckedScratch { + file: guard, claim, .. + } = staged.check(&binding, Instant::now()).unwrap(); + // The caller ends before the VMM, which inherited the claim's handle, has opened the image, + // which closes the guard: the VMM's copy of the handle keeps the claim. + drop(guard); + assert_eq!( + failure(staged.check(&binding, Instant::now())), + claimed_failure(&staged.image()) + ); + // Without the guard, nothing keeps the image from being renamed, as after authenticated Ready. + let moved = staged.other("moved.ext4"); + fs::rename(staged.image(), &moved).unwrap(); + fs::rename(&moved, staged.image()).unwrap(); + drop(claim); + drop(staged.claim(&binding)); +} + +#[test] +fn a_check_that_waited_for_a_claim_compares_the_image_again() { + // Another launch holds the image: on Linux the descriptor of its check, which holds the claim, + // and on Windows, as its VMM does once its guard is gone, an open for reading and writing. + let staged = Staged::new(); + let binding = staged.bind(); + #[cfg(target_os = "linux")] + let holder = staged.check(&binding, Instant::now()).unwrap().into_file(); + #[cfg(windows)] + let holder = OpenOptions::new() + .read(true) + .write(true) + .open(staged.image()) + .unwrap(); + let waiting = { + let (binding, path) = (binding.clone(), staged.pure()); + std::thread::spawn(move || { + binding.check_with(&path, Instant::now() + Duration::from_secs(30), WRITE_ONLY) + }) + }; + // Once the check has compared the image and waits, the holder resizes the image and lets it + // go: the check fails rather than return the image that changed. + std::thread::sleep(Duration::from_millis(300)); + holder.set_len(CONTENT.len() as u64 + 1).unwrap(); + drop(holder); + assert_eq!( + failure(waiting.join().unwrap()), + changed_failure(&staged.image(), "length") + ); + // The failed check holds no claim. + drop(staged.claim(&staged.bind())); +} + +#[test] +fn the_checked_image_is_the_object_that_the_check_compared() { + let staged = Staged::new(); + let binding = staged.bind(); + let checked = staged.check(&binding, Instant::now()).unwrap(); + let replacement = staged.other("replacement.ext4"); + fs::write(&replacement, CONTENT).unwrap(); + // On Windows, the guard keeps anything from removing, renaming, or replacing the image while + // it is held, and shares OpenVMM's open of the image for reading and writing. + #[cfg(windows)] + { + assert!(fs::remove_file(staged.image()).is_err()); + assert!(fs::rename(staged.image(), staged.other("moved.ext4")).is_err()); + assert!(fs::rename(&replacement, staged.image()).is_err()); + let mut vmm = OpenOptions::new() + .read(true) + .write(true) + .open(staged.image()) + .unwrap(); + let mut content = Vec::new(); + vmm.read_to_end(&mut content).unwrap(); + assert_eq!(content, CONTENT); + vmm.write_all(b"!").unwrap(); + } + #[cfg(target_os = "linux")] + fs::rename(&replacement, staged.image()).unwrap(); + + // What the caller holds is still the object that the check compared, whatever has its path + // now. + let seal = FileSeal::of(checked.file()).unwrap(); + assert_eq!( + (seal.device(), seal.file_id(), seal.kind()), + (binding.device(), binding.file_id(), FileKind::Regular) + ); + let file = checked.into_file(); + assert!(!inheritable(&file)); + drop(file); + + #[cfg(windows)] + fs::rename(&replacement, staged.image()).unwrap(); + assert_eq!( + failure(staged.check(&binding, Instant::now())), + changed_failure(&staged.image(), "identity") + ); +} + +/// Stages a scratch image, binds it, applies `change`, and returns the failure of the check on a +/// host where reads of content fail, with the staged image. +fn stale_after(change: impl FnOnce(&Staged)) -> ((ErrorKind, String), Staged) { + let staged = Staged::new(); + let binding = staged.bind(); + change(&staged); + (failure(staged.check(&binding, Instant::now())), staged) +} + +#[test] +fn checks_fail_without_reading_when_the_image_changes() { + // Another file in its place, even with the same content, has another identity. + let (failed, staged) = stale_after(|staged| { + let copy = staged.other("copy.ext4"); + fs::copy(staged.image(), ©).unwrap(); + fs::rename(©, staged.image()).unwrap(); + }); + assert_eq!(failed, changed_failure(&staged.image(), "identity")); + + // So has a directory in its place, whose kind differs too. The image stays, under another + // name, so that the directory cannot take its inode number, which is its file ID on Linux. + let (failed, staged) = stale_after(|staged| { + fs::rename(staged.image(), staged.other("moved.ext4")).unwrap(); + fs::create_dir(staged.image()).unwrap(); + }); + assert_eq!(failed, changed_failure(&staged.image(), "identity")); + + // A change of length changes the guest's block device. + let (failed, staged) = stale_after(|staged| { + let mut file = OpenOptions::new() + .append(true) + .open(staged.image()) + .unwrap(); + file.write_all(b"!").unwrap(); + }); + assert_eq!(failed, changed_failure(&staged.image(), "length")); + + // A binding of another kind, scheme, or version fails, as a binding that names another image + // would. + let staged = Staged::new(); + let value = serde_json::to_value(staged.bind()).unwrap(); + let edited = |field: &str, replacement: serde_json::Value| -> ScratchBinding { + let mut value = value.clone(); + value[field] = replacement; + serde_json::from_value(value).unwrap() + }; + assert_eq!( + failure(staged.check(&edited("kind", "directory".into()), Instant::now())), + changed_failure(&staged.image(), "kind") + ); + let other_scheme = if cfg!(windows) { "linux" } else { "windows" }; + assert_eq!( + failure(staged.check(&edited("scheme", other_scheme.into()), Instant::now())), + changed_failure(&staged.image(), "seal scheme") + ); + assert_eq!( + failure(staged.check(&edited("version", 2.into()), Instant::now())), + ( + ErrorKind::Unavailable, + format!( + "the scratch binding of {} has version 2, which this build does not check", + shown(&staged.image()) + ) + ) + ); + + // A missing image is unavailable, as a launch's own check of its images finds it. + let (failed, staged) = stale_after(|staged| fs::remove_file(staged.image()).unwrap()); + assert_eq!( + failed, + ( + ErrorKind::Unavailable, + format!("ext4 scratch image not found: {}", shown(&staged.image())) + ) + ); +} + +#[test] +fn checks_pass_after_writes_that_keep_the_length() { + // The VMM writes the image and moves its times, which the binding does not record. + let staged = Staged::new(); + let binding = staged.bind(); + let mut file = OpenOptions::new().write(true).open(staged.image()).unwrap(); + file.write_all(b"S").unwrap(); + file.set_modified(SystemTime::now() + Duration::from_secs(60)) + .unwrap(); + drop(file); + drop(staged.check(&binding, Instant::now()).unwrap()); +} diff --git a/nvx-rs/src/host/seal.rs b/nvx-rs/src/host/seal.rs index b64ab41fb..91f5e75dc 100644 --- a/nvx-rs/src/host/seal.rs +++ b/nvx-rs/src/host/seal.rs @@ -185,7 +185,7 @@ impl FileSeal { /// A file ID, which the serde form of a seal writes as 32 lowercase hexadecimal digits. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -struct FileId(u128); +pub(crate) struct FileId(pub(crate) u128); impl fmt::Display for FileId { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -229,6 +229,14 @@ pub(crate) fn open_read(path: &Path) -> io::Result { imp::open_read(path) } +/// Returns the final path of the open `file`, as `GetFinalPathNameByHandleW` gives it: the +/// absolute path, in its `\\?\` form, that names the file with every link and junction on the way +/// to it resolved. +#[cfg(windows)] +pub(crate) fn final_path(file: &File) -> io::Result { + imp::final_path(file) +} + /// Opens `path` for its metadata alone, so that no read or mapping of its content can succeed /// through the file: with `O_PATH` on Linux, and on Windows with `FILE_READ_ATTRIBUTES` alone, /// sharing every access. diff --git a/nvx-rs/src/host/seal/windows.rs b/nvx-rs/src/host/seal/windows.rs index 5ffaff288..ea6236c20 100644 --- a/nvx-rs/src/host/seal/windows.rs +++ b/nvx-rs/src/host/seal/windows.rs @@ -1,18 +1,19 @@ //! The Windows seal, from the file information of the open handle, and the opens of sealed files. -use std::ffi::c_void; +use std::ffi::{OsString, c_void}; use std::fs::{File, OpenOptions}; use std::io; use std::mem; +use std::os::windows::ffi::OsStringExt; use std::os::windows::fs::OpenOptionsExt; use std::os::windows::io::AsRawHandle; -use std::path::Path; +use std::path::{Path, PathBuf}; use windows_sys::Win32::Foundation::{FILETIME, GetLastError, HANDLE}; use windows_sys::Win32::Storage::FileSystem::{ BY_HANDLE_FILE_INFORMATION, FILE_ATTRIBUTE_DIRECTORY, FILE_FLAG_BACKUP_SEMANTICS, FILE_ID_INFO, FILE_SHARE_READ, FILE_TYPE_DISK, FILE_TYPE_UNKNOWN, FileIdInfo, GetFileInformationByHandle, - GetFileInformationByHandleEx, GetFileType, + GetFileInformationByHandleEx, GetFileType, GetFinalPathNameByHandleW, VOLUME_NAME_DOS, }; use super::{FileId, FileKind, FileSeal, SEAL_VERSION, SealScheme, SealTime}; @@ -107,6 +108,34 @@ pub(super) fn open_read(path: &Path) -> io::Result { File::open(path) } +/// See [`super::final_path`]. +pub(super) fn final_path(file: &File) -> io::Result { + let handle = file.as_raw_handle() as HANDLE; + let mut buffer = vec![0u16; 260]; + loop { + // SAFETY: the handle is open for as long as `file` is borrowed, and the buffer holds + // `buffer.len()` units. + let length = unsafe { + GetFinalPathNameByHandleW( + handle, + buffer.as_mut_ptr(), + buffer.len() as u32, + VOLUME_NAME_DOS, + ) + } as usize; + if length == 0 { + return Err(io::Error::last_os_error()); + } + // The path fits, and the length leaves out the NUL that follows it; or the buffer is too + // small, and the length is what the path needs, with its NUL. + if length < buffer.len() { + buffer.truncate(length); + return Ok(PathBuf::from(OsString::from_wide(&buffer))); + } + buffer.resize(length, 0); + } +} + /// See [`super::open_metadata_only`]. Without `FILE_READ_DATA`, Windows refuses every read of /// the handle, and every mapping of the file through it. #[cfg(test)] @@ -164,4 +193,27 @@ mod tests { } ); } + + #[test] + fn final_paths_hold_no_nul_whatever_their_length() { + use std::os::windows::ffi::OsStrExt; + + // A path shorter than the first buffer, and one longer, which takes a second call with + // the length, terminator included, that the first one asked for. + let directory = tempfile::tempdir().unwrap(); + let mut deep = directory.path().to_owned(); + while deep.as_os_str().len() < 300 { + deep.push("a-directory-that-lengthens-the-final-path"); + } + std::fs::create_dir_all(&deep).unwrap(); + for path in [directory.path().join("short"), deep.join("long")] { + std::fs::write(&path, b"").unwrap(); + let file = File::open(&path).unwrap(); + let found = final_path(&file).unwrap(); + assert!(!found.as_os_str().encode_wide().any(|unit| unit == 0)); + assert_eq!(found, std::fs::canonicalize(&path).unwrap()); + } + let file = File::open(deep.join("long")).unwrap(); + assert!(final_path(&file).unwrap().as_os_str().len() > 260); + } } diff --git a/nvx-rs/src/host/supervisor/child.rs b/nvx-rs/src/host/supervisor/child.rs index cdc60f20c..53d1560b8 100644 --- a/nvx-rs/src/host/supervisor/child.rs +++ b/nvx-rs/src/host/supervisor/child.rs @@ -164,9 +164,10 @@ impl VmmCommand<'_> { /// Record the VMM's identity right after the spawn, with /// [`Launch::record_process`](crate::host::store::Launch::record_process), as /// [`launch`](super::launch) does. Fails with [`io::ErrorKind::InvalidInput`] on Linux for a -/// descriptor of a standard stream, 0 to 2, or one that is not close-on-exec, and with -/// [`io::ErrorKind::Unsupported`] for any descriptor or handle on hosts other than Linux and -/// Windows. +/// descriptor of a standard stream, 0 to 2, or one that is not close-on-exec, and on Windows for +/// a batch file, which Windows would run through `cmd.exe` with arguments that it reads as shell +/// syntax; and with [`io::ErrorKind::Unsupported`] for any descriptor or handle on hosts other +/// than Linux and Windows. pub fn spawn( command: &VmmCommand<'_>, capability: &[u8; CAPABILITY_LEN], diff --git a/nvx-rs/src/host/sys/mod.rs b/nvx-rs/src/host/sys/mod.rs index 840135412..a791dea6a 100644 --- a/nvx-rs/src/host/sys/mod.rs +++ b/nvx-rs/src/host/sys/mod.rs @@ -20,8 +20,12 @@ use nix::unistd::{getegid, geteuid}; #[cfg(feature = "cli")] pub(crate) use interrupt::{interrupted, watch_interrupts, write_interruptibly}; +#[cfg(all(feature = "cli", target_os = "linux"))] +pub(crate) use process::inherit_only; #[cfg(target_os = "linux")] -pub(crate) use process::{LAST_STANDARD_STREAM, PidFd, detach, process_exists}; +pub(crate) use process::{ + LAST_STANDARD_STREAM, PidFd, detach, move_above_standard_streams, process_exists, +}; #[cfg(all(test, target_os = "linux"))] pub(crate) use process::{clear_close_on_exec, mark_listed_descriptors_on_spawn}; #[cfg(target_os = "linux")] diff --git a/nvx-rs/src/host/sys/process.rs b/nvx-rs/src/host/sys/process.rs index c9d79afee..08071086d 100644 --- a/nvx-rs/src/host/sys/process.rs +++ b/nvx-rs/src/host/sys/process.rs @@ -1,5 +1,6 @@ //! Process descriptors, and the hook that detaches a spawned process from its caller. +use std::fs::File; use std::io; use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; use std::os::unix::process::CommandExt; @@ -24,6 +25,23 @@ use super::Errno; /// the descriptors that a detached spawn marks close-on-exec are the ones above it. pub(crate) const LAST_STANDARD_STREAM: RawFd = libc::STDERR_FILENO; +/// Moves `file` to a descriptor above [`LAST_STANDARD_STREAM`], under which a spawn can pass it to +/// its process, if it is not there already: as when the caller had closed a standard stream, whose +/// descriptor an open then took. +/// +/// The new descriptor is a close-on-exec copy of the old one, which is then closed. It shares the +/// file's open file description, and with it the file's offset and `flock` locks. +pub(crate) fn move_above_standard_streams(file: &mut File) -> io::Result<()> { + if file.as_raw_fd() > LAST_STANDARD_STREAM { + return Ok(()); + } + let copy = fcntl(&*file, FcntlArg::F_DUPFD_CLOEXEC(LAST_STANDARD_STREAM + 1))?; + // SAFETY: fcntl returned a fresh descriptor, which nothing else owns. Replacing the file + // closes the old descriptor. + *file = File::from(unsafe { OwnedFd::from_raw_fd(copy) }); + Ok(()) +} + /// A pidfd: an owned descriptor that refers to one process. Signals sent through it reach that /// process or fail, even once another process reuses its ID. pub(crate) struct PidFd(OwnedFd); @@ -117,11 +135,27 @@ pub(crate) fn process_exists(pid: u32) -> io::Result { /// exec, so the descriptors stay close-on-exec in the caller, and no process that another thread /// spawns meanwhile inherits them. The caller keeps them open until the spawn returns. pub(crate) fn detach(command: &mut Command, inherit: Vec) { + restrict_inheritance(command, true, inherit); +} + +/// Makes `command` start its process as [`detach`] does, but in the caller's session, as a child +/// that shares the caller's terminal. +#[cfg(feature = "cli")] +pub(crate) fn inherit_only(command: &mut Command, inherit: Vec) { + restrict_inheritance(command, false, inherit); +} + +/// Makes `command` start its process in a new session if `new_session`, with every descriptor of +/// the caller above standard error close-on-exec, except the descriptors `inherit`, as +/// [`detach`] describes. +fn restrict_inheritance(command: &mut Command, new_session: bool, inherit: Vec) { // SAFETY: the hook makes only async-signal-safe system calls and allocates nothing, as code // that runs between fork and exec must: `inherit` was allocated before the fork. unsafe { command.pre_exec(move || { - setsid()?; + if new_session { + setsid()?; + } mark_descriptors_close_on_exec()?; inherit_descriptors(&inherit) }); diff --git a/nvx-rs/src/testing/fake_openvmm.rs b/nvx-rs/src/testing/fake_openvmm.rs index 58792fe3a..d9a844240 100644 --- a/nvx-rs/src/testing/fake_openvmm.rs +++ b/nvx-rs/src/testing/fake_openvmm.rs @@ -53,7 +53,11 @@ //! `--microvm-sandbox-block` option selects: read-only `distro`, `runtime`, and `custom` layers //! and a `scratch` disk, each an existing file, with `nvx_sandbox=1`, `nvx_entrypoint=`, and //! `nvx_hostname=` on the kernel command line, and shares through one `--mount` or the children -//! of an aggregate at `/run/nvx/shares`. Its guest advertises the features of the sandbox agent, +//! of an aggregate at `/run/nvx/shares`. Like OpenVMM, it opens each block device at the path that +//! its option names, a layer read-only and the scratch disk for reading and writing, and holds +//! them until it exits; it reads them whole, and records what each held, with its role and path, +//! in `fake-openvmm--blocks.json` next to the kernel. The `fake_block_delay_ms=MS` token +//! delays the opens. Its guest advertises the features of the sandbox agent, //! [`GuestFeatures::SANDBOX_AGENT`], and the guest targets of its directory shares are //! directories that a workload can enter. With `--microvm-lifecycle managed`, it serves the //! control console as above. With `--microvm-lifecycle one-shot`, it reads no capability and @@ -186,6 +190,12 @@ struct Options { args_dump: PathBuf, /// Path that receives the mounted-host-path dump. maps_dump: PathBuf, + /// Path that receives what the sandbox block devices held. + blocks_dump: PathBuf, + /// The sandbox block devices: their roles and paths, and whether each is read-only. + blocks: Vec, + /// Artificial delay before the fake opens the sandbox block devices. + block_delay: Duration, /// Status with which to stop immediately after launch, when requested. exit_on_start: Option, /// Status with which a one-shot workload ends instead of its own, when requested. @@ -249,6 +259,28 @@ struct Mapped { directory: bool, } +/// A sandbox block device of the command line. +#[derive(Debug, Clone)] +struct SandboxBlock { + /// Its role: `distro`, `runtime`, `custom`, or `scratch`. + role: String, + /// The path of its file, as the option names it. + path: PathBuf, + /// Whether the guest sees it read-only, as a layer. + read_only: bool, +} + +/// What a sandbox block device held when the fake opened it. +#[derive(serde::Serialize)] +struct OpenedBlock { + /// Its role. + role: String, + /// The path at which the fake opened it. + path: String, + /// Its content, with invalid UTF-8 replaced. + content: String, +} + /// Runs the fake OpenVMM with the process's arguments and standard input, as an executable's /// `main` does, and returns its exit status. pub fn main() -> ExitCode { @@ -269,6 +301,15 @@ pub fn main() -> ExitCode { eprintln!("{program}: failing the launch as requested"); return ExitCode::from(code); } + thread::sleep(options.block_delay); + // OpenVMM opens its disks as it builds the VM, and holds them until it exits. + let _blocks = match open_blocks(&options) { + Ok(blocks) => blocks, + Err(message) => { + eprintln!("{program}: {message}"); + return ExitCode::from(2); + } + }; if options.lifecycle == Lifecycle::OneShot { let status = run_one_shot(&options); let _ = io::stdout().flush(); @@ -298,6 +339,44 @@ pub fn main() -> ExitCode { } } +/// Opens the sandbox block devices of `options` as OpenVMM opens them, a layer read-only and the +/// scratch disk for reading and writing, reads each whole, and records what each held in the +/// blocks dump. Returns the open files, which the caller holds until it exits, as OpenVMM holds +/// its disks. +fn open_blocks(options: &Options) -> Result, String> { + let mut files = Vec::new(); + let mut opened = Vec::new(); + for block in &options.blocks { + let failed = |error: io::Error| { + format!( + "cannot open the {} block device {}: {error}", + block.role, + block.path.display() + ) + }; + let mut file = OpenOptions::new() + .read(true) + .write(!block.read_only) + .open(&block.path) + .map_err(failed)?; + let mut content = Vec::new(); + file.read_to_end(&mut content).map_err(failed)?; + opened.push(OpenedBlock { + role: block.role.clone(), + path: block.path.display().to_string(), + content: String::from_utf8_lossy(&content).into_owned(), + }); + files.push(file); + } + if !opened.is_empty() { + let _ = fs::write( + &options.blocks_dump, + serde_json::to_vec(&opened).unwrap_or_default(), + ); + } + Ok(files) +} + /// Runs the entrypoint of a one-shot sandbox with its output on the fake's own, as OpenVMM runs /// the guest on its console, writes the outcome report, and returns the status with which /// OpenVMM exits: the workload's. @@ -491,11 +570,12 @@ fn parse(program: &str, arguments: &[String]) -> Result { .filter_map(|token| token.strip_prefix(name)) .collect() }; - let (maps, children, share_targets, workload) = if sandbox { + let (maps, children, share_targets, workload, blocks) = if sandbox { if !tokens.contains(&"nvx_sandbox=1") { return Err("a sandbox block device requires nvx_sandbox=1".to_owned()); } - check_sandbox_blocks(values.get("--microvm-sandbox-block").into_iter().flatten())?; + let blocks = + check_sandbox_blocks(values.get("--microvm-sandbox-block").into_iter().flatten())?; let mut workload: Vec = match token_values("nvx_entrypoint=").as_slice() { [entrypoint] => vec![(*entrypoint).to_owned()], _ => return Err("the kernel command line must set one nvx_entrypoint".to_owned()), @@ -517,7 +597,7 @@ fn parse(program: &str, arguments: &[String]) -> Result { _ => return Err(format!("invalid nvx_share={share}")), } } - (0, children, share_targets, workload) + (0, children, share_targets, workload, blocks) } else { for option in ["--mount", "--mount-owner"] { if values.contains_key(option) { @@ -544,7 +624,7 @@ fn parse(program: &str, arguments: &[String]) -> Result { _ => return Err(format!("{MAPS_TOKEN} must be given at most once")), }; let children = mapped_children(&values, maps)?; - (maps, children, Vec::new(), Vec::new()) + (maps, children, Vec::new(), Vec::new(), Vec::new()) }; for option in ["--mount-deny", "--mount-allow", "--mount-write"] { for path in values.get(option).into_iter().flatten() { @@ -601,6 +681,9 @@ fn parse(program: &str, arguments: &[String]) -> Result { endpoint, args_dump: kernel.with_file_name(format!("fake-openvmm-{token}.json")), maps_dump: kernel.with_file_name(format!("fake-openvmm-{token}-maps.json")), + blocks_dump: kernel.with_file_name(format!("fake-openvmm-{token}-blocks.json")), + blocks, + block_delay: millis("fake_block_delay_ms").unwrap_or_default(), report, hypervisor: hypervisor.to_owned(), exit_on_start: knob("fake_exit_on_start").and_then(|value| value.parse().ok()), @@ -626,26 +709,36 @@ fn parse(program: &str, arguments: &[String]) -> Result { /// Checks the sandbox block devices of a sandbox: each layer, `ROLE:file:PATH,ro` with a role of /// `distro`, `runtime`, or `custom`, at most once each, and one scratch disk, `scratch:file:PATH`, -/// each an existing regular file. -fn check_sandbox_blocks<'a>(blocks: impl Iterator) -> Result<(), String> { +/// each an existing regular file, and returns them in order. +fn check_sandbox_blocks<'a>( + blocks: impl Iterator, +) -> Result, String> { let mut roles = BTreeSet::new(); + let mut checked = Vec::new(); for block in blocks { let invalid = || format!("invalid --microvm-sandbox-block {block}"); let (role, rest) = block.split_once(':').ok_or_else(invalid)?; let path = rest.strip_prefix("file:").ok_or_else(invalid)?; - let path = match role { - "distro" | "runtime" | "custom" => path.strip_suffix(",ro").ok_or_else(invalid)?, - "scratch" => path, + let (path, read_only) = match role { + "distro" | "runtime" | "custom" => { + (path.strip_suffix(",ro").ok_or_else(invalid)?, true) + } + "scratch" => (path, false), _ => return Err(invalid()), }; if !roles.insert(role) || !Path::new(path).is_file() { return Err(invalid()); } + checked.push(SandboxBlock { + role: role.to_owned(), + path: PathBuf::from(path), + read_only, + }); } if !roles.contains("scratch") || roles.len() < 2 { return Err("a sandbox needs a layer and a scratch disk".to_owned()); } - Ok(()) + Ok(checked) } /// Returns the host directories of a sandbox's shares, for the checks of its policy paths, and diff --git a/nvx-rs/tests/checked_handoff.rs b/nvx-rs/tests/checked_handoff.rs new file mode 100644 index 000000000..86082cc4c --- /dev/null +++ b/nvx-rs/tests/checked_handoff.rs @@ -0,0 +1,876 @@ +//! The checked handoff of a prepared launch, through the public API of `nvx::host::sandbox` and +//! `nvx::host::supervisor`, as an orchestrator uses them: it stages and binds a distro layer and a +//! scratch image, checks them, prepares the launch, and spawns the VMM with the prepared launch's +//! arguments and allowlist. +//! +//! The VMM is this test executable, run again as a fixture. It reads the capability that +//! `nvx::host::supervisor::spawn` passes on its standard input, as OpenVMM does, reports its +//! descriptors on Linux and that it started, and, once a file named `go` appears in its working +//! directory, opens each block device that an argument names as OpenVMM opens it, a layer +//! read-only and the scratch image for reading and writing, reads each whole, writes the scratch +//! image, and reports what each held. It holds them until a file named `exit` appears in its +//! working directory, which ends it before `go` too. On Linux, the executable also runs as an +//! orchestrator fixture that closed its standard input before it checked the images. +#![cfg(all(feature = "host", any(target_os = "linux", windows)))] + +use std::env; +use std::ffi::OsString; +use std::fs::{self, File, OpenOptions}; +use std::io::{self, Read, Write}; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::thread; +use std::time::{Duration, Instant}; + +use nvx::host::sandbox::distro::{DISTRO_POLICY_REVISION, DistroBinding}; +use nvx::host::sandbox::scratch::{CLAIM_RELEASE_WAIT, ScratchBinding}; +use nvx::host::sandbox::{LayerRole, PreparedLaunch, SandboxLaunch, SandboxLayer, SandboxSettings}; +use nvx::host::supervisor::{self, Launched, VmmCommand}; +use nvx::protocol::CAPABILITY_LEN; + +/// File whose presence in its working directory marks a VMM fixture. Without it, the fixture +/// returns at once, as it does when `cargo test -- --ignored` runs it. +const VMM_MARKER: &str = "vmm-fixture"; +/// File whose appearance in its working directory lets a VMM fixture open its block devices. +const GO_MARKER: &str = "go"; +/// File that a VMM fixture writes once it has started, after its report of its descriptors. +const STARTED_REPORT: &str = "started"; +/// File whose appearance in its working directory ends a VMM fixture. +const EXIT_MARKER: &str = "exit"; +/// File in which a VMM fixture reports what its block devices held, a line for each: its role +/// and its content, escaped. +const BLOCKS_REPORT: &str = "blocks"; +/// File in which a VMM fixture reports its descriptors on Linux, a line for each: its number and +/// the object that it holds. +#[cfg(target_os = "linux")] +const DESCRIPTORS_REPORT: &str = "descriptors"; +/// Prefix of an argument of a VMM fixture that holds the value of a `--microvm-sandbox-block` +/// option of the prepared launch. Libtest takes the argument for a test name that matches no +/// test. +const BLOCK_PREFIX: &str = "block="; +/// File whose presence in its working directory marks a launcher fixture. Without it, the fixture +/// returns at once. +#[cfg(target_os = "linux")] +const LAUNCHER_MARKER: &str = "launcher-fixture"; +/// File that a launcher fixture writes once the VMM that it launched found the checked images. +#[cfg(target_os = "linux")] +const LAUNCHED_REPORT: &str = "launched"; +/// The capability of the VMMs of these tests. +const CAPABILITY: [u8; CAPABILITY_LEN] = [0x5a; CAPABILITY_LEN]; +/// Longest wait for a fixture to answer. +const TIMEOUT: Duration = Duration::from_secs(60); +/// Longest life of a VMM fixture that nothing ends, so that a failed test leaves no process. +const VMM_LIFETIME: Duration = Duration::from_secs(180); +/// The UUID of the staged layer. +const UUID: &str = "11111111-1111-1111-1111-111111111111"; +/// The content of the staged distro layer. +const DISTRO: &str = "distro layer\n"; +/// The content of the staged scratch image. +const SCRATCH: &str = "scratch image\n"; + +/// The VMM fixture: reads its capability as OpenVMM does, reports its descriptors on Linux, then, +/// once [`GO_MARKER`] appears, opens and reads each block device that an argument names, writes +/// the scratch image, reports what each held, and runs until [`EXIT_MARKER`] appears. +#[test] +#[ignore = "fixture that the handoff tests run as a VMM"] +fn vmm_fixture() { + let directory = env::current_dir().unwrap(); + if !directory.join(VMM_MARKER).exists() { + return; + } + let mut capability = [0; CAPABILITY_LEN]; + io::stdin().read_exact(&mut capability).unwrap(); + assert_eq!(capability, CAPABILITY); + #[cfg(target_os = "linux")] + report_descriptors(&directory); + write_whole(&directory.join(STARTED_REPORT), ""); + // A test that failed before it let the fixture go ends it. + let (go, exit) = (directory.join(GO_MARKER), directory.join(EXIT_MARKER)); + let deadline = Instant::now() + VMM_LIFETIME; + while !go.exists() { + if exit.exists() || Instant::now() >= deadline { + return; + } + thread::sleep(Duration::from_millis(10)); + } + let mut held = Vec::new(); + let mut report = String::new(); + for argument in env::args() { + let Some(block) = argument.strip_prefix(BLOCK_PREFIX) else { + continue; + }; + let (role, rest) = block.split_once(":file:").unwrap(); + let (path, read_only) = match rest.strip_suffix(",ro") { + Some(path) => (path, true), + None => (rest, false), + }; + let mut file = OpenOptions::new() + .read(true) + .write(!read_only) + .open(path) + .unwrap(); + let mut content = String::new(); + file.read_to_string(&mut content).unwrap(); + if !read_only { + file.write_all(b"written by the VMM\n").unwrap(); + } + report.push_str(&format!("{role} {}\n", content.escape_default())); + held.push(file); + } + write_whole(&directory.join(BLOCKS_REPORT), &report); + wait_for(&directory.join(EXIT_MARKER), VMM_LIFETIME); +} + +/// Writes `text` to the file at `path`, which appears whole. +fn write_whole(path: &Path, text: &str) { + let temporary = path.with_extension("tmp"); + fs::write(&temporary, text).unwrap(); + fs::rename(&temporary, path).unwrap(); +} + +/// Waits until the file at `path` exists, for at most `timeout`, and returns whether it does. +fn wait_for(path: &Path, timeout: Duration) -> bool { + let deadline = Instant::now() + timeout; + while !path.exists() { + if Instant::now() >= deadline { + return false; + } + thread::sleep(Duration::from_millis(10)); + } + true +} + +/// Writes the descriptors of this VMM fixture, but for the one that lists them, to the file +/// [`DESCRIPTORS_REPORT`] in `directory`. +#[cfg(target_os = "linux")] +fn report_descriptors(directory: &Path) { + // The descriptor that lists the others holds this directory. + let listing = PathBuf::from(format!("/proc/{}/fd", std::process::id())); + let mut descriptors: Vec<(usize, PathBuf)> = fs::read_dir("/proc/self/fd") + .unwrap() + .filter_map(|entry| { + let entry = entry.unwrap(); + let number = entry.file_name().to_str().unwrap().parse().unwrap(); + let object = fs::read_link(entry.path()).ok()?; + (object != listing).then_some((number, object)) + }) + .collect(); + descriptors.sort(); + let report: String = descriptors + .iter() + .map(|(number, object)| format!("{number} {}\n", object.display())) + .collect(); + write_whole(&directory.join(DESCRIPTORS_REPORT), &report); +} + +/// Returns the arguments that run only the fixture `name` of this test executable. +fn fixture_arguments(name: &str) -> Vec { + [ + "--exact", + name, + "--ignored", + "--nocapture", + "--test-threads=1", + ] + .map(OsString::from) + .to_vec() +} + +/// Returns the arguments that pass a VMM fixture the `--microvm-sandbox-block` values of +/// `prepared`. +fn block_arguments(prepared: &PreparedLaunch) -> Vec { + prepared + .openvmm_arguments() + .windows(2) + .filter(|pair| pair[0] == "--microvm-sandbox-block") + .map(|pair| OsString::from(format!("{BLOCK_PREFIX}{}", pair[1]))) + .collect() +} + +/// Starts a VMM fixture in `vmm`'s directory with the block devices and the allowlist of +/// `prepared`, and waits until it has started. +fn start_vmm(vmm: &mut VmmDirectory, prepared: &PreparedLaunch) { + let mut arguments = fixture_arguments("vmm_fixture"); + arguments.extend(block_arguments(prepared)); + let mut command = VmmCommand::new(env::current_exe().unwrap(), arguments, vmm.path()); + command.inherit = prepared.inherited(); + let log = File::create(vmm.path().join("vmm.log")).unwrap(); + vmm.launched = Some(supervisor::spawn(&command, &CAPABILITY, log).unwrap()); + vmm.report(STARTED_REPORT); +} + +/// Returns the report of a VMM fixture whose block devices are the staged images. +fn staged_blocks() -> String { + format!( + "distro {}\nscratch {}\n", + DISTRO.escape_default(), + SCRATCH.escape_default() + ) +} + +/// The working directory of a VMM fixture. Dropping it ends the fixture, and the launched VMM +/// that it holds, so that a failed test leaves no process behind. +struct VmmDirectory { + /// The launched VMM, once the test spawned it. + launched: Option, + /// The directory, which is removed after the VMM exits. + directory: tempfile::TempDir, +} + +impl VmmDirectory { + /// Creates a directory that marks a VMM fixture. + fn new() -> Self { + let directory = tempfile::tempdir().unwrap(); + File::create(directory.path().join(VMM_MARKER)).unwrap(); + Self { + launched: None, + directory, + } + } + + /// Returns the path of the directory. + fn path(&self) -> &Path { + self.directory.path() + } + + /// Waits for the VMM's report `name`, and returns it. + fn report(&self, name: &str) -> String { + let path = self.path().join(name); + assert!(wait_for(&path, TIMEOUT), "the VMM did not report {name}"); + fs::read_to_string(path).unwrap() + } +} + +impl Drop for VmmDirectory { + fn drop(&mut self) { + let _ = File::create(self.path().join(EXIT_MARKER)); + if let Some(launched) = self.launched.take() { + if let Ok(process) = launched.process() { + let _ = process.wait(Instant::now() + TIMEOUT); + } + launched.kill_and_wait(); + } + } +} + +/// A distro layer and a scratch image, staged in a temporary directory. +struct Staged { + /// The temporary directory. + directory: tempfile::TempDir, +} + +impl Staged { + /// Stages a distro layer and a scratch image. + fn new() -> Self { + let staged = Self { + directory: tempfile::tempdir().unwrap(), + }; + fs::write(staged.distro(), DISTRO).unwrap(); + fs::write(staged.scratch(), SCRATCH).unwrap(); + staged + } + + /// Returns the path of the distro layer. + fn distro(&self) -> PathBuf { + self.directory.path().join("distro.erofs") + } + + /// Returns the path of the scratch image. + fn scratch(&self) -> PathBuf { + self.directory.path().join("scratch.ext4") + } + + /// Returns the launch of the distro layer over the scratch image. + fn launch(&self) -> SandboxLaunch { + launch_of(&self.distro(), &self.scratch()) + } +} + +/// Returns the launch of the distro layer at `distro` over the scratch image at `scratch`. +fn launch_of(distro: &Path, scratch: &Path) -> SandboxLaunch { + SandboxLaunch::new( + vec![SandboxLayer::new( + LayerRole::Distro, + distro.to_str().unwrap(), + UUID, + )], + scratch.to_str().unwrap(), + SandboxSettings::default(), + ) + .unwrap() +} + +/// Sets its flag when it is dropped, also while a failure unwinds, so that a thread that runs +/// until the flag is set stops. +struct SetOnDrop<'a>(&'a AtomicBool); + +impl Drop for SetOnDrop<'_> { + fn drop(&mut self) { + self.0.store(true, Ordering::SeqCst); + } +} + +/// Spawns processes through `std::process` until `done`, as other threads of an orchestrator do, +/// and counts them in `spawned`. Returns, on Linux, those that held a descriptor of an object of +/// `checked`, which no process but the VMM may inherit. +/// +/// On Linux, each process lists its own descriptors once its program runs. The spawn returns +/// before the exec has closed the descriptors that are close-on-exec, so a listing that this +/// process took then could show descriptors that never reach the program. +fn spawn_others(done: &AtomicBool, spawned: &AtomicUsize, checked: &[PathBuf]) -> Vec { + #[cfg_attr(not(target_os = "linux"), allow(unused_mut))] + let mut leaks = Vec::new(); + while !done.load(Ordering::SeqCst) { + #[cfg(target_os = "linux")] + { + let output = Command::new("ls") + .args(["-l", "/proc/self/fd/"]) + .stdin(Stdio::null()) + .stderr(Stdio::null()) + .output() + .unwrap(); + assert!(output.status.success()); + for line in String::from_utf8_lossy(&output.stdout).lines() { + if let Some((_, object)) = line.split_once(" -> ") + && checked.iter().any(|checked| checked.as_os_str() == object) + { + leaks.push(format!("a process held {object}")); + } + } + } + #[cfg(windows)] + { + let _ = checked; + let mut child = sleeper().spawn().unwrap(); + let _ = child.kill(); + child.wait().unwrap(); + } + spawned.fetch_add(1, Ordering::SeqCst); + } + leaks +} + +/// Returns a command that runs for a few seconds, without standard streams. +#[cfg(windows)] +fn sleeper() -> Command { + let mut command = Command::new("ping"); + command + .args(["-n", "5", "127.0.0.1"]) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()); + command +} + +/// Returns the object that the descriptor `number` of this process holds, as its link in `/proc` +/// names it. +#[cfg(target_os = "linux")] +fn this_object(number: i32) -> PathBuf { + fs::read_link(format!("/proc/self/fd/{number}")).unwrap() +} + +/// Makes `pipe` inheritable, or not, as code that spawns without an allowlist does around its +/// spawns. +#[cfg(target_os = "linux")] +fn set_inheritable(pipe: &io::PipeWriter, inheritable: bool) { + use nix::fcntl::{FcntlArg, FdFlag, fcntl}; + + let flags = if inheritable { + FdFlag::empty() + } else { + FdFlag::FD_CLOEXEC + }; + fcntl(pipe, FcntlArg::F_SETFD(flags)).unwrap(); +} + +/// Makes `pipe` inheritable, or not, as code that spawns without an allowlist does around its +/// spawns. +#[cfg(windows)] +fn set_inheritable(pipe: &io::PipeWriter, inheritable: bool) { + use std::os::windows::io::AsRawHandle; + + use windows_sys::Win32::Foundation::{HANDLE, HANDLE_FLAG_INHERIT, SetHandleInformation}; + + let flags = if inheritable { HANDLE_FLAG_INHERIT } else { 0 }; + // SAFETY: the handle is open. + let set = + unsafe { SetHandleInformation(pipe.as_raw_handle() as HANDLE, HANDLE_FLAG_INHERIT, flags) }; + assert_ne!(set, 0, "{}", io::Error::last_os_error()); +} + +/// Returns whether the handle `value` of process `pid` refers to the object of this process's +/// handle `value`, as a handle that the process inherited from this one does. +#[cfg(windows)] +fn holds(pid: u32, value: usize) -> bool { + use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}; + use std::ptr; + + use windows_sys::Win32::Foundation::{ + CompareObjectHandles, DUPLICATE_SAME_ACCESS, DuplicateHandle, HANDLE, + }; + use windows_sys::Win32::System::Threading::{ + GetCurrentProcess, OpenProcess, PROCESS_DUP_HANDLE, + }; + + let ours = value as HANDLE; + // SAFETY: OpenProcess has no memory-safety preconditions. + let process = unsafe { OpenProcess(PROCESS_DUP_HANDLE, 0, pid) }; + assert!( + !process.is_null(), + "cannot open process {pid}: {}", + io::Error::last_os_error() + ); + // SAFETY: the handle is new, and nothing else owns it. + let process = unsafe { OwnedHandle::from_raw_handle(process) }; + let mut copy = ptr::null_mut(); + // SAFETY: the process handle may duplicate the process's handles, the value is looked up in + // that process only, and the output pointer is valid. + let duplicated = unsafe { + DuplicateHandle( + process.as_raw_handle(), + ours, + GetCurrentProcess(), + &mut copy, + 0, + 0, + DUPLICATE_SAME_ACCESS, + ) + } != 0; + // A value that names no handle of the process names none of ours. + if !duplicated { + return false; + } + // SAFETY: the copy is new, and nothing else owns it. + let copy = unsafe { OwnedHandle::from_raw_handle(copy) }; + // SAFETY: both handles are open. + unsafe { CompareObjectHandles(copy.as_raw_handle(), ours) != 0 } +} + +#[test] +fn a_prepared_launch_hands_the_vmm_its_checked_images_alone() { + let staged = Staged::new(); + let launch = staged.launch(); + // Staging binds the images, where hashing belongs; the launch checks them from their metadata + // alone, and claims the scratch image. + let binding = DistroBinding::verify(launch.distro()).unwrap(); + let scratch_binding = ScratchBinding::bind(launch.scratch()).unwrap(); + let distro = binding + .check(launch.distro(), DISTRO_POLICY_REVISION) + .unwrap(); + let scratch = scratch_binding + .check(launch.scratch(), Instant::now() + CLAIM_RELEASE_WAIT) + .unwrap(); + let prepared = PreparedLaunch::new( + launch.bound_plan(&binding).unwrap(), + distro, + Some(scratch), + "", + ) + .unwrap(); + + // Something replaces both images between the checks and the VMM's opens: on Linux it can, + // and the VMM opens the checked files anyway; on Windows the checked files keep it from + // doing so. + for (path, replacement) in [ + (staged.distro(), "replaced distro\n"), + (staged.scratch(), "replaced scratch\n"), + ] { + let new = path.with_extension("new"); + fs::write(&new, replacement).unwrap(); + let replaced = fs::rename(&new, &path); + assert_eq!( + replaced.is_ok(), + cfg!(target_os = "linux"), + "{}: {replaced:?}", + path.display() + ); + } + + #[cfg(target_os = "linux")] + let checked: Vec = { + use std::os::fd::{AsFd, AsRawFd}; + + prepared + .inherited() + .iter() + .map(|inherited| this_object(inherited.as_fd().as_raw_fd())) + .collect() + }; + #[cfg(windows)] + let checked: Vec = Vec::new(); + let mut vmm = VmmDirectory::new(); + let mut arguments = fixture_arguments("vmm_fixture"); + let blocks = block_arguments(&prepared); + assert_eq!(blocks.len(), 2); + arguments.extend(blocks); + let mut command = VmmCommand::new(env::current_exe().unwrap(), arguments, vmm.path()); + command.inherit = prepared.inherited(); + // A descriptor or handle that this process leaves inheritable, as code that spawns without an + // allowlist does, while another thread spawns processes. + let (_unrelated_reader, unrelated) = io::pipe().unwrap(); + let done = AtomicBool::new(false); + let spawned = AtomicUsize::new(0); + let leaks = thread::scope(|scope| { + let spawner = scope.spawn(|| spawn_others(&done, &spawned, &checked)); + // Stops the other thread when the VMM has started, or when a failure unwinds the scope, + // which then waits for the thread. + let stop = SetOnDrop(&done); + let deadline = Instant::now() + TIMEOUT; + while spawned.load(Ordering::SeqCst) == 0 { + assert!( + Instant::now() < deadline && !spawner.is_finished(), + "the other thread spawned nothing" + ); + thread::sleep(Duration::from_millis(10)); + } + set_inheritable(&unrelated, true); + let log = File::create(vmm.path().join("vmm.log")).unwrap(); + let launched = supervisor::spawn(&command, &CAPABILITY, log); + set_inheritable(&unrelated, false); + vmm.launched = Some(launched.unwrap()); + // The other thread spawns throughout the VMM's start. + vmm.report(STARTED_REPORT); + drop(stop); + spawner.join().unwrap() + }); + assert!(spawned.load(Ordering::SeqCst) > 0); + assert!(leaks.is_empty(), "{leaks:?}"); + let pid = vmm.launched.as_ref().unwrap().id(); + + // The VMM holds its standard streams and the checked files under their numbers, and nothing + // else of this process's: on Linux, the checked descriptors, which the arguments name; on + // Windows, the claim's handle of the scratch image, which no argument names. + #[cfg(target_os = "linux")] + { + use std::os::fd::{AsFd, AsRawFd}; + + let _ = pid; + let reported = vmm.report(DESCRIPTORS_REPORT); + let mut expected: Vec = prepared + .inherited() + .iter() + .map(|inherited| { + let number = inherited.as_fd().as_raw_fd(); + format!("{number} {}", this_object(number).display()) + }) + .collect(); + let log = fs::canonicalize(vmm.path().join("vmm.log")).unwrap(); + let lines: Vec<&str> = reported.lines().collect(); + assert_eq!(lines.len(), 5, "{reported}"); + assert!(lines[0].starts_with("0 pipe:"), "{reported}"); + assert_eq!(lines[1], format!("1 {}", log.display())); + assert_eq!(lines[2], format!("2 {}", log.display())); + expected.sort(); + let mut inherited: Vec = lines[3..].iter().map(|line| (*line).to_owned()).collect(); + inherited.sort(); + assert_eq!(inherited, expected); + for object in &checked { + assert!( + object.to_string_lossy().ends_with(" (deleted)"), + "the checked file was not replaced at its path: {}", + object.display() + ); + } + } + #[cfg(windows)] + { + use std::os::windows::io::{AsHandle, AsRawHandle}; + + let inherited: Vec = prepared + .inherited() + .iter() + .map(|inherited| inherited.as_handle().as_raw_handle() as usize) + .collect(); + assert_eq!(inherited.len(), 1); + assert!( + holds(pid, inherited[0]), + "the VMM does not hold the scratch image's claim" + ); + let mut ours = vec![ + prepared.distro().file().as_raw_handle() as usize, + unrelated.as_raw_handle() as usize, + ]; + ours.extend( + prepared + .scratch() + .map(|scratch| scratch.file().as_raw_handle() as usize), + ); + for value in ours { + assert_ne!(value, inherited[0]); + assert!(!holds(pid, value), "the VMM holds handle {value:#x}"); + } + } + + // The VMM opens its block devices as OpenVMM does, and finds the objects that the launch + // checked. + File::create(vmm.path().join(GO_MARKER)).unwrap(); + let report = vmm.report(BLOCKS_REPORT); + assert_eq!(report, staged_blocks()); + + // On Windows, while the launch holds the checked files, nothing writes the distro layer, and + // nothing removes or renames either image, while the VMM reads and writes the scratch image. + // Once the guest is ready, the launch releases them, and what the VMM holds, its own opens + // and the scratch image's claim that it inherited, shares renames. + #[cfg(windows)] + { + let moved = |path: &Path| path.with_extension("moved"); + assert!( + OpenOptions::new() + .append(true) + .open(staged.distro()) + .is_err() + ); + for path in [staged.distro(), staged.scratch()] { + assert!(fs::rename(&path, moved(&path)).is_err()); + assert!(fs::remove_file(&path).is_err()); + } + drop(prepared); + for path in [staged.distro(), staged.scratch()] { + fs::rename(&path, moved(&path)).unwrap(); + fs::rename(moved(&path), &path).unwrap(); + } + } + #[cfg(target_os = "linux")] + drop(prepared); + + // The VMM wrote the checked scratch image, whatever has its path now. + File::create(vmm.path().join(EXIT_MARKER)).unwrap(); + let launched = vmm.launched.take().unwrap(); + assert!( + launched + .process() + .unwrap() + .wait(Instant::now() + TIMEOUT) + .unwrap() + ); + launched.kill_and_wait(); + let scratch = fs::read_to_string(staged.scratch()).unwrap(); + if cfg!(windows) { + assert_eq!(scratch, format!("{SCRATCH}written by the VMM\n")); + } else { + assert_eq!(scratch, "replaced scratch\n"); + } +} + +#[test] +fn a_vmm_keeps_the_scratch_images_claim_once_its_launcher_is_gone() { + use nvx::ErrorKind; + + let staged = Staged::new(); + let launch = staged.launch(); + let binding = DistroBinding::verify(launch.distro()).unwrap(); + let scratch_binding = ScratchBinding::bind(launch.scratch()).unwrap(); + let check_scratch = + || scratch_binding.check(launch.scratch(), Instant::now() + CLAIM_RELEASE_WAIT); + let prepared = PreparedLaunch::new( + launch.bound_plan(&binding).unwrap(), + binding + .check(launch.distro(), DISTRO_POLICY_REVISION) + .unwrap(), + Some(check_scratch().unwrap()), + "", + ) + .unwrap(); + let mut vmm = VmmDirectory::new(); + start_vmm(&mut vmm, &prepared); + + // The launcher ends before the VMM has opened the images, as when it crashes, which closes + // the checked files as dropping the prepared launch does. The VMM, which inherited the + // scratch image's claim, keeps it: another sandbox cannot claim the image that the VMM is + // about to write. + drop(prepared); + let claimed = check_scratch().unwrap_err(); + assert_eq!(claimed.kind(), ErrorKind::TimedOut, "{claimed}"); + + // The claim lasts while the VMM writes the image, which changes its length here, so that a + // binding of the written image tells the claim apart, and it ends with the VMM. + File::create(vmm.path().join(GO_MARKER)).unwrap(); + assert_eq!(vmm.report(BLOCKS_REPORT), staged_blocks()); + let written = ScratchBinding::bind(launch.scratch()).unwrap(); + assert_ne!(written, scratch_binding); + let check_written = || written.check(launch.scratch(), Instant::now() + CLAIM_RELEASE_WAIT); + let claimed = check_written().unwrap_err(); + assert_eq!(claimed.kind(), ErrorKind::TimedOut, "{claimed}"); + File::create(vmm.path().join(EXIT_MARKER)).unwrap(); + let launched = vmm.launched.take().unwrap(); + assert!( + launched + .process() + .unwrap() + .wait(Instant::now() + TIMEOUT) + .unwrap() + ); + launched.kill_and_wait(); + drop(check_written().unwrap()); +} + +/// Makes `link` a junction to the directory `target`, which needs no privilege. +#[cfg(windows)] +fn junction(link: &Path, target: &Path) { + let status = Command::new("cmd") + .args(["/C", "mklink", "/J"]) + .arg(link) + .arg(target) + .stdout(Stdio::null()) + .status() + .unwrap(); + assert!(status.success(), "cannot link {}", link.display()); +} + +#[cfg(windows)] +#[test] +fn a_vmm_finds_the_checked_images_past_replaced_links_from_another_directory() { + // The images, and decoys of them, in the target's temporary directory, which the plan names + // through a junction, relative to this process's directory when the target is below it. + let root = tempfile::tempdir_in(env!("CARGO_TARGET_TMPDIR")).unwrap(); + let real = root.path().join("real"); + let decoy = root.path().join("decoy"); + for (directory, distro, scratch) in [ + (&real, DISTRO, SCRATCH), + (&decoy, "decoy distro\n", "decoy scratch\n"), + ] { + fs::create_dir(directory).unwrap(); + fs::write(directory.join("distro.erofs"), distro).unwrap(); + fs::write(directory.join("scratch.ext4"), scratch).unwrap(); + } + let link = root.path().join("link"); + junction(&link, &real); + let named = link + .strip_prefix(env::current_dir().unwrap()) + .map_or_else(|_| link.clone(), Path::to_path_buf); + let launch = launch_of(&named.join("distro.erofs"), &named.join("scratch.ext4")); + let binding = DistroBinding::verify(launch.distro()).unwrap(); + let scratch_binding = ScratchBinding::bind(launch.scratch()).unwrap(); + let distro = binding + .check(launch.distro(), DISTRO_POLICY_REVISION) + .unwrap(); + let scratch = scratch_binding + .check(launch.scratch(), Instant::now() + CLAIM_RELEASE_WAIT) + .unwrap(); + let prepared = PreparedLaunch::new( + launch.bound_plan(&binding).unwrap(), + distro, + Some(scratch), + "", + ) + .unwrap(); + + // The arguments name the images by their final paths, which go through no link. + let final_path = |name: &str| { + let path = fs::canonicalize(real.join(name)).unwrap(); + path.to_str().unwrap().to_owned() + }; + assert_eq!( + block_arguments(&prepared), + [ + format!( + "{BLOCK_PREFIX}distro:file:{},ro", + final_path("distro.erofs") + ), + format!("{BLOCK_PREFIX}scratch:file:{}", final_path("scratch.ext4")), + ] + .map(OsString::from) + ); + + // Then the link leads to the decoys, and nothing renames the directory of the images, which + // the launch holds. + fs::remove_dir(&link).unwrap(); + junction(&link, &decoy); + assert!(fs::rename(&real, root.path().join("moved")).is_err()); + + // The VMM, which starts in another directory, finds the checked images. + let mut vmm = VmmDirectory::new(); + start_vmm(&mut vmm, &prepared); + File::create(vmm.path().join(GO_MARKER)).unwrap(); + assert_eq!(vmm.report(BLOCKS_REPORT), staged_blocks()); + drop(prepared); + drop(vmm); +} + +/// The launcher fixture: an orchestrator that closes its standard input, as a daemon may, before +/// it checks the scratch image, which the check then opens as descriptor 0, under which no spawn +/// passes it. It prepares the launch, launches a VMM fixture, and writes [`LAUNCHED_REPORT`] once +/// the VMM found the checked images. +#[cfg(target_os = "linux")] +#[test] +#[ignore = "fixture that a handoff test runs as an orchestrator without standard input"] +fn launcher_fixture() { + use std::os::fd::{AsRawFd, FromRawFd, OwnedFd}; + + use nvx::ErrorKind; + + let directory = env::current_dir().unwrap(); + if !directory.join(LAUNCHER_MARKER).exists() { + return; + } + let staged = Staged::new(); + let launch = staged.launch(); + let binding = DistroBinding::verify(launch.distro()).unwrap(); + let scratch_binding = ScratchBinding::bind(launch.scratch()).unwrap(); + let distro = binding + .check(launch.distro(), DISTRO_POLICY_REVISION) + .unwrap(); + // SAFETY: descriptor 0, standard input, is open, and nothing in this process reads it. + nix::unistd::close(unsafe { OwnedFd::from_raw_fd(0) }).unwrap(); + let scratch = scratch_binding + .check(launch.scratch(), Instant::now() + CLAIM_RELEASE_WAIT) + .unwrap(); + assert_eq!(scratch.file().as_raw_fd(), 0); + let prepared = PreparedLaunch::new( + launch.bound_plan(&binding).unwrap(), + distro, + Some(scratch), + "", + ) + .unwrap(); + + // The image moved above the standard streams, descriptor 0 is closed again, and the image's + // claim moved with it. + let moved = prepared.scratch().unwrap().file().as_raw_fd(); + assert!(moved > 2, "{moved}"); + assert!(fs::read_link("/proc/self/fd/0").is_err()); + assert_eq!( + scratch_binding + .check(launch.scratch(), Instant::now()) + .unwrap_err() + .kind(), + ErrorKind::TimedOut + ); + + // The VMM inherits the image under its new number, and finds the checked images. + let mut vmm = VmmDirectory::new(); + start_vmm(&mut vmm, &prepared); + let descriptors = vmm.report(DESCRIPTORS_REPORT); + assert!( + descriptors + .lines() + .any(|line| line.starts_with(&format!("{moved} "))), + "{descriptors}" + ); + File::create(vmm.path().join(GO_MARKER)).unwrap(); + assert_eq!(vmm.report(BLOCKS_REPORT), staged_blocks()); + drop(prepared); + drop(vmm); + write_whole(&directory.join(LAUNCHED_REPORT), ""); +} + +#[cfg(target_os = "linux")] +#[test] +fn a_launch_passes_a_checked_file_that_took_a_standard_streams_descriptor() { + let directory = tempfile::tempdir().unwrap(); + File::create(directory.path().join(LAUNCHER_MARKER)).unwrap(); + let output = Command::new(env::current_exe().unwrap()) + .args(fixture_arguments("launcher_fixture")) + .current_dir(directory.path()) + .stdin(Stdio::null()) + .output() + .unwrap(); + assert!( + output.status.success(), + "{}{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + assert!(directory.path().join(LAUNCHED_REPORT).exists()); +} diff --git a/nvx-rs/tests/cli.rs b/nvx-rs/tests/cli.rs index 5c33f6bcc..2e6121208 100644 --- a/nvx-rs/tests/cli.rs +++ b/nvx-rs/tests/cli.rs @@ -8,7 +8,8 @@ //! `nvx-rs --root ROOT`, and every step must exit with the recorded status, print the recorded //! output, and leave the recorded files, after the root, process IDs, and random identifiers are //! replaced with the placeholders that the script used, and the devices, file IDs, and times that -//! the distro binding of `config.json` records with `{int}` and `{hex32}` as well. Now that +//! the bindings of `config.json` record, and the numbers of the descriptors that name a launch's +//! checked files, with `{int}` and `{hex32}` as well. Now that //! `nvx.py sandbox` runs `nvx-rs`, the recordings are its reference: a deliberate change of //! behavior updates them by hand. #![cfg(all( @@ -129,7 +130,14 @@ impl Root { ] { text = replace_after(&text, prefix, is_hex, Some(32), "{hex32}"); } - text + // On Linux, a launch names its checked files by the descriptors that OpenVMM inherits. + replace_after( + &text, + "/proc/self/fd/", + |c| c.is_ascii_digit(), + None, + "{int}", + ) } /// Creates the tree of the fixture. @@ -1667,6 +1675,495 @@ fn start_refuses_a_configuration_without_a_valid_network_policy() { } } +#[test] +fn start_refuses_a_scratch_image_that_changed_after_provision() { + // Start checks the binding of the scratch image that provision recorded against the image's + // metadata alone, before it launches OpenVMM: an image that changed needs a new provision. + let root = populated_root(); + let scratch = root.path.join("scratch.ext4"); + let shown = scratch.to_str().unwrap(); + let changed = |field: &str| { + format!( + "{shown} changed after its scratch binding was made: its {field} differs\ + {PROVISION_AGAIN}" + ) + }; + // Another length changes the guest's block device. + provisioned(&root, "resized"); + let mut data = fs::read(&scratch).unwrap(); + data.extend_from_slice(b"more\n"); + fs::write(&scratch, &data).unwrap(); + refused_start(&root, "resized", &changed("length")); + // Another file in its place, even with the same content, has another identity. + provisioned(&root, "replaced"); + let replacement = root.path.join("scratch.new"); + fs::write(&replacement, &data).unwrap(); + fs::rename(&replacement, &scratch).unwrap(); + refused_start(&root, "replaced", &changed("identity")); + // The launch's own check of its images still finds a missing image first. + provisioned(&root, "removed"); + fs::remove_file(&scratch).unwrap(); + refused_start( + &root, + "removed", + &format!("ext4 scratch image not found: {shown}"), + ); + // A new provision binds the image as it is now. Writes of the same length, as OpenVMM's, + // keep the binding. + fs::write(&scratch, &data).unwrap(); + provisioned(&root, "again"); + data[0] = b'S'; + fs::write(&scratch, &data).unwrap(); + cycle(&root, "again"); +} + +#[test] +fn start_refuses_a_configuration_without_a_valid_scratch_binding() { + let root = populated_root(); + let scratch = root.path.join("scratch.ext4"); + let scratch = scratch.to_str().unwrap(); + let malformed = "sandbox configuration is malformed".to_owned(); + let changed = |field: &str| { + format!( + "{scratch} changed after its scratch binding was made: its {field} differs\ + {PROVISION_AGAIN}" + ) + }; + let cases: [(&str, ConfigEdit, String); 6] = [ + ( + "missing", + |config| { + config.as_object_mut().unwrap().remove("scratch_binding"); + }, + format!("sandbox configuration has no scratch binding{PROVISION_AGAIN}"), + ), + ( + "incomplete", + |config| { + config["scratch_binding"] + .as_object_mut() + .unwrap() + .remove("length"); + }, + malformed.clone(), + ), + ( + "unknown", + |config| config["scratch_binding"]["owner"] = json!("another"), + malformed, + ), + ( + "version", + |config| config["scratch_binding"]["version"] = json!(2), + format!( + "the scratch binding of {scratch} has version 2, which this build does not \ + check{PROVISION_AGAIN}" + ), + ), + ( + "kind", + |config| config["scratch_binding"]["kind"] = json!("directory"), + changed("kind"), + ), + ( + "scheme", + |config| { + config["scratch_binding"]["scheme"] = + json!(if cfg!(windows) { "linux" } else { "windows" }); + }, + changed("seal scheme"), + ), + ]; + for (state, edit, message) in cases { + provisioned(&root, state); + let path = root.path.join(state).join("config.json"); + let mut config: Value = serde_json::from_str(&fs::read_to_string(&path).unwrap()).unwrap(); + edit(&mut config); + fs::write(&path, serde_json::to_string_pretty(&config).unwrap()).unwrap(); + refused_start(&root, state, &message); + } +} + +#[test] +fn a_running_sandbox_claims_its_scratch_image() { + // Two sandboxes may record the same scratch image, but only one at a time runs on it: a start + // claims the image until its OpenVMM exits, and a second start, or a run, fails meanwhile. + let root = populated_root(); + let scratch = root.path.join("scratch.ext4"); + let claimed = format!( + "error: another sandbox claims the ext4 scratch image {}\n", + scratch.to_str().unwrap() + ); + provisioned(&root, "first"); + provisioned(&root, "second"); + let output = run(&root, &lifecycle("start", &root, "first")); + assert!(output.status.success(), "{}", text(&output.stderr)); + + let output = run(&root, &lifecycle("start", &root, "second")); + assert_eq!(output.status.code(), Some(1), "{}", text(&output.stderr)); + assert_eq!(text(&output.stderr), claimed); + assert_eq!( + state_entries(&root, "second"), + ["config.json", "sandbox.lock"] + ); + let one_shot: Vec = vec![ + "sandbox".into(), + "run".into(), + "--layer".into(), + format!( + "distro,{},11111111-1111-1111-1111-111111111111", + root.path.join("distro.erofs").to_str().unwrap() + ), + "--scratch".into(), + scratch.to_str().unwrap().to_owned(), + "--entrypoint".into(), + "/bin/true".into(), + ]; + let output = run(&root, &one_shot); + assert_eq!(output.status.code(), Some(1), "{}", text(&output.stderr)); + assert_eq!(text(&output.stderr), claimed); + + // Once the first sandbox stops, the image is free again. + let output = run(&root, &lifecycle("stop", &root, "first")); + assert!(output.status.success(), "{}", text(&output.stderr)); + cycle(&root, "second"); + let output = run(&root, &one_shot); + assert!(output.status.success(), "{}", text(&output.stderr)); + let output = run(&root, &lifecycle("deprovision", &root, "first")); + assert!(output.status.success(), "{}", text(&output.stderr)); +} + +/// Waits until the file at `path` exists. +fn wait_for(path: &Path) { + let deadline = std::time::Instant::now() + Duration::from_secs(60); + while !path.exists() { + assert!( + std::time::Instant::now() < deadline, + "{} did not appear", + path.display() + ); + thread::sleep(Duration::from_millis(10)); + } +} + +#[test] +fn start_hands_openvmm_the_images_that_it_checked() { + // Images that something replaces after the start checked them and before OpenVMM opens them: + // on Linux, OpenVMM opens the checked files through the descriptors that it inherits, whatever + // has their paths now; on Windows, the checked files keep anything from replacing the images + // until the guest is ready. + let root = populated_root(); + let output = run( + &root, + &provision( + &root, + "state", + "fake_block_delay_ms=1500 fake_boot_delay_ms=1500", + ), + ); + assert!(output.status.success(), "{}", text(&output.stderr)); + let distro = root.path.join("distro.erofs"); + let scratch = root.path.join("scratch.ext4"); + #[cfg(windows)] + let moved = |path: &Path| path.with_extension("moved"); + let start = Command::new(NVX_RS) + .arg("--root") + .arg(&root.path) + .args(lifecycle("start", &root, "state")) + .current_dir(&root.path) + .env_remove("NVX_OPENVMM") + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + + // OpenVMM has started, and has not opened its images yet. + wait_for(&root.path.join("build").join("fake-openvmm-state.json")); + let blocks = root + .path + .join("build") + .join("fake-openvmm-state-blocks.json"); + assert!(!blocks.exists()); + for (path, replacement) in [ + (&distro, "replaced distro\n"), + (&scratch, "replaced scratch\n"), + ] { + let new = path.with_extension("new"); + fs::write(&new, replacement).unwrap(); + let replaced = fs::rename(&new, path); + assert_eq!( + replaced.is_ok(), + cfg!(unix), + "{}: {replaced:?}", + path.display() + ); + } + // On Windows, while the start holds the checked files, nothing writes the distro layer, and + // nothing removes or renames either image, even once OpenVMM reads and writes the scratch + // image. + #[cfg(windows)] + for opened in [false, true] { + if opened { + wait_for(&blocks); + } + assert!(fs::OpenOptions::new().append(true).open(&distro).is_err()); + for path in [&distro, &scratch] { + assert!(fs::rename(path, moved(path)).is_err()); + assert!(fs::remove_file(path).is_err()); + } + } + + let output = start.wait_with_output().unwrap(); + assert!(output.status.success(), "{}", text(&output.stderr)); + // OpenVMM opened the objects that the start checked. + let opened: Value = serde_json::from_str(&fs::read_to_string(&blocks).unwrap()).unwrap(); + let opened: Vec<(&str, &str)> = opened + .as_array() + .unwrap() + .iter() + .map(|block| { + ( + block["role"].as_str().unwrap(), + block["content"].as_str().unwrap(), + ) + }) + .collect(); + assert_eq!(opened, [("distro", "distro\n"), ("scratch", "scratch\n")]); + // Once the guest is ready, the start releases the checked files: on Windows, the images can be + // renamed again, as OpenVMM's own opens share it. + #[cfg(windows)] + for path in [&distro, &scratch] { + fs::rename(path, moved(path)).unwrap(); + fs::rename(moved(path), path).unwrap(); + } + for operation in ["stop", "deprovision"] { + let output = run(&root, &lifecycle(operation, &root, "state")); + assert!( + output.status.success(), + "{operation}: {}", + text(&output.stderr) + ); + } +} + +/// Makes `handle` inheritable, or not, as code that spawns without a handle list does around its +/// spawns. +#[cfg(windows)] +fn set_inheritable(handle: &impl std::os::windows::io::AsRawHandle, inheritable: bool) { + use windows_sys::Win32::Foundation::{HANDLE_FLAG_INHERIT, SetHandleInformation}; + + let flags = if inheritable { HANDLE_FLAG_INHERIT } else { 0 }; + // SAFETY: the handle is open. + let set = unsafe { SetHandleInformation(handle.as_raw_handle(), HANDLE_FLAG_INHERIT, flags) }; + assert_ne!(set, 0, "{}", std::io::Error::last_os_error()); +} + +/// Returns whether the handle `value` of process `pid` refers to the object of this process's +/// handle `value`, as a handle that the process inherited from this one, directly or through its +/// parent, does. +#[cfg(windows)] +fn holds(pid: u32, value: usize) -> bool { + use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}; + use std::ptr; + + use windows_sys::Win32::Foundation::{ + CompareObjectHandles, DUPLICATE_SAME_ACCESS, DuplicateHandle, HANDLE, + }; + use windows_sys::Win32::System::Threading::{ + GetCurrentProcess, OpenProcess, PROCESS_DUP_HANDLE, + }; + + let ours = value as HANDLE; + // SAFETY: OpenProcess has no memory-safety preconditions. + let process = unsafe { OpenProcess(PROCESS_DUP_HANDLE, 0, pid) }; + assert!( + !process.is_null(), + "cannot open process {pid}: {}", + std::io::Error::last_os_error() + ); + // SAFETY: the handle is new, and nothing else owns it. + let process = unsafe { OwnedHandle::from_raw_handle(process) }; + let mut copy = ptr::null_mut(); + // SAFETY: the process handle may duplicate the process's handles, the value is looked up in + // that process only, and the output pointer is valid. + let duplicated = unsafe { + DuplicateHandle( + process.as_raw_handle(), + ours, + GetCurrentProcess(), + &mut copy, + 0, + 0, + DUPLICATE_SAME_ACCESS, + ) + } != 0; + // A value that names no handle of the process names none of ours. + if !duplicated { + return false; + } + // SAFETY: the copy is new, and nothing else owns it. + let copy = unsafe { OwnedHandle::from_raw_handle(copy) }; + // SAFETY: both handles are open. + unsafe { CompareObjectHandles(copy.as_raw_handle(), ours) != 0 } +} + +/// Returns the IDs of the running processes that process `parent` started from an executable +/// named `name`. +#[cfg(windows)] +fn children_named(parent: u32, name: &str) -> Vec { + use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}; + + use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE; + use windows_sys::Win32::System::Diagnostics::ToolHelp::{ + CreateToolhelp32Snapshot, PROCESSENTRY32W, Process32FirstW, Process32NextW, + TH32CS_SNAPPROCESS, + }; + + // SAFETY: CreateToolhelp32Snapshot has no memory-safety preconditions. + let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }; + assert_ne!( + snapshot, + INVALID_HANDLE_VALUE, + "{}", + std::io::Error::last_os_error() + ); + // SAFETY: the snapshot handle is new, and nothing else owns it. + let snapshot = unsafe { OwnedHandle::from_raw_handle(snapshot) }; + // SAFETY: PROCESSENTRY32W is a plain C structure for which all-zero is a valid value. + let mut entry: PROCESSENTRY32W = unsafe { std::mem::zeroed() }; + entry.dwSize = std::mem::size_of::() as u32; + let mut children = Vec::new(); + // SAFETY: the snapshot is open, and the entry holds its own size. + let mut found = unsafe { Process32FirstW(snapshot.as_raw_handle(), &mut entry) } != 0; + while found { + let file = &entry.szExeFile; + let length = file + .iter() + .position(|&unit| unit == 0) + .unwrap_or(file.len()); + if entry.th32ParentProcessID == parent + && String::from_utf16_lossy(&file[..length]).eq_ignore_ascii_case(name) + { + children.push(entry.th32ProcessID); + } + // SAFETY: as above. + found = unsafe { Process32NextW(snapshot.as_raw_handle(), &mut entry) } != 0; + } + children +} + +#[cfg(windows)] +#[test] +fn a_run_passes_openvmm_no_handle_of_its_caller() { + use std::os::windows::io::AsRawHandle; + + // A handle that the CLI inherits from its caller, as from a launcher that started it without a + // handle list, and that `std::process::Command` would pass on to OpenVMM. + let (_reader, unrelated) = std::io::pipe().unwrap(); + let root = populated_root(); + let path = |name: &str| root.path.join(name).to_str().unwrap().to_owned(); + let mut command = Command::new(NVX_RS); + command + .arg("--root") + .arg(&root.path) + .args([ + "sandbox", + "run", + "--layer", + &format!( + "distro,{},11111111-1111-1111-1111-111111111111", + path("distro.erofs") + ), + "--scratch", + &path("scratch.ext4"), + "--entrypoint", + "/bin/sleep", + "--arg", + "60", + ]) + .current_dir(&root.path) + .env_remove("NVX_OPENVMM") + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()); + set_inheritable(&unrelated, true); + let cli = command.spawn(); + set_inheritable(&unrelated, false); + let mut cli = cli.unwrap(); + + // Once OpenVMM has started, the CLI holds the handle, and OpenVMM does not. + wait_for(&root.path.join("build").join("fake-openvmm-.json")); + let value = unrelated.as_raw_handle() as usize; + let openvmm = children_named(cli.id(), "openvmm.exe"); + let cli_holds = holds(cli.id(), value); + let openvmm_holds: Vec = openvmm.iter().map(|&pid| holds(pid, value)).collect(); + for &pid in &openvmm { + kill(u64::from(pid)); + } + if openvmm.is_empty() { + let _ = cli.kill(); + } + let _ = cli.wait(); + assert_eq!(openvmm.len(), 1, "{openvmm:?}"); + assert!(cli_holds, "the CLI did not inherit the handle"); + assert_eq!( + openvmm_holds, + [false], + "OpenVMM inherited a handle of the CLI's caller" + ); +} + +/// Writes a batch file below the root that leaves a marker if Windows runs it, and returns the +/// environment that names it as OpenVMM, the marker, and the error with which the CLI refuses it. +#[cfg(windows)] +fn batch_openvmm(root: &Root) -> (BTreeMap, PathBuf, String) { + let marker = root.path.join("ran"); + let wrapper = root.path.join("openvmm.cmd"); + fs::write(&wrapper, format!("@echo ran>\"{}\"\r\n", marker.display())).unwrap(); + let environment = BTreeMap::from([( + "NVX_OPENVMM".to_owned(), + wrapper.to_str().unwrap().to_owned(), + )]); + let refusal = format!( + "error: cannot start the batch file {}: Windows runs batch files through cmd.exe, which \ + would read their arguments as shell syntax\n", + wrapper.display() + ); + (environment, marker, refusal) +} + +#[cfg(windows)] +#[test] +fn a_run_refuses_a_batch_file_as_openvmm() { + // cmd.exe would read the `&` of the scratch image's path as a second command. + let root = populated_root(); + let (environment, marker, refusal) = batch_openvmm(&root); + let scratch = root.path.join("scratch&echo.ext4"); + fs::copy(root.path.join("scratch.ext4"), &scratch).unwrap(); + let layer = format!( + "distro,{},11111111-1111-1111-1111-111111111111", + root.path.join("distro.erofs").display() + ); + let arguments: Vec = [ + "sandbox", + "run", + "--layer", + &layer, + "--scratch", + scratch.to_str().unwrap(), + "--entrypoint", + "/bin/true", + ] + .map(str::to_owned) + .to_vec(); + let output = nvx_rs(&root, &arguments, &environment); + assert_eq!(output.status.code(), Some(1), "{}", text(&output.stderr)); + assert_eq!(text(&output.stderr), refusal); + thread::sleep(Duration::from_millis(500)); + assert!(!marker.exists()); +} + #[test] fn artifacts_default_to_the_checkout_that_holds_the_executable() { let output = Command::new(NVX_RS) diff --git a/nvx-rs/tests/fixtures/cli.json b/nvx-rs/tests/fixtures/cli.json index aa41cb7f2..d1faa550c 100644 --- a/nvx-rs/tests/fixtures/cli.json +++ b/nvx-rs/tests/fixtures/cli.json @@ -117,7 +117,7 @@ {"name":"validation: relative mount target","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","data,{root}/share"],"status":1,"stdout":"","stderr":"error: invalid sandbox mount target 'data': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','\n"}]}, {"name":"validation: missing mount host","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/missing"],"status":1,"stdout":"","stderr":"error: sandbox mount host path is not a plain directory: {root}/missing\n"}]}, {"name":"validation: invalid mount mode","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share,rx"],"status":1,"stdout":"","stderr":"error: unsupported --mount mode 'rx'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)\n"}]}, -{"name":"validation: deny outside share","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share","--mount-deny","{root}/share2"],"status":2,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --mount /data,{root}/share,ro --mount-deny {root}/share2 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox'\n","stderr":"openvmm: invalid --mount-deny {root}/share2\n"}]}, +{"name":"validation: deny outside share","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share","--mount-deny","{root}/share2"],"status":2,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:/proc/self/fd/{int},ro --microvm-sandbox-block scratch:file:/proc/self/fd/{int} --microvm-workload-identity 65534:65534 --mount /data,{root}/share,ro --mount-deny {root}/share2 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox'\n","stderr":"openvmm: invalid --mount-deny {root}/share2\n"}]}, {"name":"validation: allow outside denied path","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share","--mount-allow","{root}/share/hidden/shown"],"status":1,"stdout":"","stderr":"error: --mount-allow exposes a path inside a --mount-deny path of the same share, which has none\n"}]}, {"name":"validation: write in read-only share","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share,ro","--mount-write","{root}/share/writable"],"status":1,"stdout":"","stderr":"error: --mount-write narrows the writes of a read-write share; --mount /data is read-only\n"}]}, {"name":"validation: reserved command-line token","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--cmdline","nvx_sandbox=0"],"status":1,"stdout":"","stderr":"error: nvx_sandbox is owned by the sandbox profile\n"}]}, @@ -137,30 +137,30 @@ {"name":"dry run: egress policy file","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--net","10.0.0.2/24","--network-profile","portable","--network-egress","deny","--network-egress-policy-file","{root}/policy.json","--dry-run"],"write":{"policy.json":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.128/25\"]}, {\"cidr\": \"2001:db8::/32\", \"protocol\": \"tcp\", \"port\": 443}], \"deny\": [{\"cidr\": \"198.51.100.0/24\", \"protocol\": \"udp\"}]}"},"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --net 10.0.0.2/24 --network-profile portable --network-egress deny --network-ingress deny --network-egress-allow 192.0.2.0/25 --network-egress-allow 2001:db8::/32:tcp:443 --network-egress-deny 198.51.100.0/24:udp --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox'\n","stderr":""}]}, {"name":"dry run: quoted arguments","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--cmdline","a=\"b c\" d","--dry-run"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'a=\"b c\" d nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox'\n","stderr":""}]}, {"name":"dry run: caller-owned share","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share","--mount-owner","caller","--dry-run"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --mount /data,{root}/share,ro --mount-owner caller --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox'\n","stderr":""}]}, -{"name":"run: echo","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/echo","--arg","hello","--arg","world"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world'\nhello world\n","stderr":""}]}, -{"name":"run: default operation","steps":[{"argv":["sandbox","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/echo","--arg","hello","--arg","world"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world'\nhello world\n","stderr":""}]}, -{"name":"run: failure","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/false"],"status":1,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/false nvx_hostname=nvx-sandbox'\n","stderr":""}]}, -{"name":"run: missing program","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/missing"],"status":127,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/missing nvx_hostname=nvx-sandbox'\n","stderr":"openvmm: /bin/missing: not found\n"}]}, -{"name":"run: exit status","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/true","--cmdline","fake_exit_status=7"],"status":7,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'fake_exit_status=7 nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/true nvx_hostname=nvx-sandbox'\n","stderr":""}]}, -{"name":"run: outcome report","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/echo","--arg","hello","--arg","world","--outcome-report","{root}/reports/openvmm-run.json"],"check":["reports/openvmm-run.json","build/fake-openvmm-reports.json"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world' --microvm-report {root}/reports/openvmm-run.json\nhello world\n","stderr":"","files":{"reports/openvmm-run.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"run\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"build/fake-openvmm-reports.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root}/distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root}/scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--microvm-lifecycle\",\"one-shot\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world\",\"--microvm-report\",\"{root}/reports/openvmm-run.json\"]"}}}]}, -{"name":"run: network outcome report","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/true","--net","10.0.0.2/24","--network-profile","portable","--network-egress","deny","--network-egress-allow","10.0.0.0/8:tcp:443","--outcome-report","{root}/reports/openvmm-run.json"],"check":["reports/openvmm-run.json"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:{root}/distro.erofs,ro --microvm-sandbox-block scratch:file:{root}/scratch.ext4 --microvm-workload-identity 65534:65534 --net 10.0.0.2/24 --network-profile portable --network-egress deny --network-ingress deny --network-egress-allow 10.0.0.0/8:tcp:443 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/true nvx_hostname=nvx-sandbox' --microvm-report {root}/reports/openvmm-run.json\n","stderr":"","files":{"reports/openvmm-run.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 1,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"allow\",\n \"mode\": \"rules\",\n \"status\": \"applied\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"run\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"}}}]}, +{"name":"run: echo","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/echo","--arg","hello","--arg","world"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:/proc/self/fd/{int},ro --microvm-sandbox-block scratch:file:/proc/self/fd/{int} --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world'\nhello world\n","stderr":""}]}, +{"name":"run: default operation","steps":[{"argv":["sandbox","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/echo","--arg","hello","--arg","world"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:/proc/self/fd/{int},ro --microvm-sandbox-block scratch:file:/proc/self/fd/{int} --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world'\nhello world\n","stderr":""}]}, +{"name":"run: failure","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/false"],"status":1,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:/proc/self/fd/{int},ro --microvm-sandbox-block scratch:file:/proc/self/fd/{int} --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/false nvx_hostname=nvx-sandbox'\n","stderr":""}]}, +{"name":"run: missing program","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/missing"],"status":127,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:/proc/self/fd/{int},ro --microvm-sandbox-block scratch:file:/proc/self/fd/{int} --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/missing nvx_hostname=nvx-sandbox'\n","stderr":"openvmm: /bin/missing: not found\n"}]}, +{"name":"run: exit status","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/true","--cmdline","fake_exit_status=7"],"status":7,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:/proc/self/fd/{int},ro --microvm-sandbox-block scratch:file:/proc/self/fd/{int} --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'fake_exit_status=7 nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/true nvx_hostname=nvx-sandbox'\n","stderr":""}]}, +{"name":"run: outcome report","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/echo","--arg","hello","--arg","world","--outcome-report","{root}/reports/openvmm-run.json"],"check":["reports/openvmm-run.json","build/fake-openvmm-reports.json"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:/proc/self/fd/{int},ro --microvm-sandbox-block scratch:file:/proc/self/fd/{int} --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world' --microvm-report {root}/reports/openvmm-run.json\nhello world\n","stderr":"","files":{"reports/openvmm-run.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"run\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"build/fake-openvmm-reports.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:/proc/self/fd/{int},ro\",\"--microvm-sandbox-block\",\"scratch:file:/proc/self/fd/{int}\",\"--microvm-workload-identity\",\"65534:65534\",\"--microvm-lifecycle\",\"one-shot\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world\",\"--microvm-report\",\"{root}/reports/openvmm-run.json\"]"}}}]}, +{"name":"run: network outcome report","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/true","--net","10.0.0.2/24","--network-profile","portable","--network-egress","deny","--network-egress-allow","10.0.0.0/8:tcp:443","--outcome-report","{root}/reports/openvmm-run.json"],"check":["reports/openvmm-run.json"],"status":0,"stdout":">> {root}/openvmm/target/release/openvmm --machine microvm --microvm-sandbox-block distro:file:/proc/self/fd/{int},ro --microvm-sandbox-block scratch:file:/proc/self/fd/{int} --microvm-workload-identity 65534:65534 --net 10.0.0.2/24 --network-profile portable --network-egress deny --network-ingress deny --network-egress-allow 10.0.0.0/8:tcp:443 --microvm-lifecycle one-shot --single-process --hypervisor kvm --memory 256M --kernel {root}/build/vmlinux --initrd {root}/build/initramfs.cpio.gz --cmdline 'nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/true nvx_hostname=nvx-sandbox' --microvm-report {root}/reports/openvmm-run.json\n","stderr":"","files":{"reports/openvmm-run.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 1,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"allow\",\n \"mode\": \"rules\",\n \"status\": \"applied\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"run\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"}}}]}, {"name":"run: network options without --net","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--host-loopback-forward","8080"],"status":1,"stdout":"","stderr":"error: network policy options require --net\n"}]}, {"name":"run: missing OpenVMM","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"remove":["openvmm/target/release/openvmm{exe}"],"status":1,"stdout":"","stderr":"error: OpenVMM release binary not found: {root}/openvmm/target/release/openvmm\n"}]}, {"name":"run: missing kernel","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"remove":["build/vmlinux"],"status":1,"stdout":"","stderr":"error: Linux direct kernel not found: {root}/build/vmlinux\n"}]}, {"name":"run: missing initramfs","steps":[{"argv":["sandbox","run","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"remove":["build/initramfs.cpio.gz"],"status":1,"stdout":"","stderr":"error: initramfs not found: {root}/build/initramfs.cpio.gz\n"}]}, -{"name":"lifecycle: minimal","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root}/distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root}/scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/echo","--arg","hello","--arg","world"],"status":0,"stdout":"hello world\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/pwd","--cwd","/tmp"],"status":0,"stdout":"/tmp\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/sh","--arg=-c","--arg","echo out; echoerr err; exit 3","--outcome-report","{root}/reports/exec.json"],"check":["reports/exec.json"],"status":3,"stdout":"out\n","stderr":"err\n","files":{"reports/exec.json":{"text":"{\n \"operation_id\": \"{hex32}\",\n \"outcome\": {\n \"category\": \"exit\",\n \"operation\": \"exec\",\n \"status_code\": 3\n },\n \"schema_version\": 1\n}\n","mode":384}}},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/usr/bin/env","--environment","A=1","--environment","B=two"],"status":0,"stdout":"A=1\nB=two\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/usr/bin/printenv","--arg","PATH","--inherit-default-environment","--environment","A=1"],"status":0,"stdout":"/usr/sbin:/usr/bin:/sbin:/bin\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/usr/bin/env","--environment-file","{root}/env.json"],"write":{"env.json":"[\"X=1\", \"Y=two words\"]"},"status":0,"stdout":"X=1\nY=two words\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/usr/bin/env","--environment-file","{root}/empty.json"],"write":{"empty.json":"[]"},"status":0,"stdout":"","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/sh","--arg=-c","--arg","sleep 5000","--exec-timeout-ms","50","--outcome-report","{root}/reports/exec-timeout.json"],"check":["reports/exec-timeout.json"],"status":124,"stdout":"","stderr":"","files":{"reports/exec-timeout.json":{"text":"{\n \"operation_id\": \"{hex32}\",\n \"outcome\": {\n \"category\": \"timeout\",\n \"operation\": \"exec\",\n \"status_code\": 124\n },\n \"schema_version\": 1\n}\n","mode":384}}},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/sh","--arg=-c","--arg","signal 9"],"status":137,"stdout":"","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/missing"],"status":127,"stdout":"","stderr":"openvmm: /bin/missing: not found\n"},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/pwd","--cwd","/missing"],"status":1,"stdout":"","stderr":"error: managed guest rejected exec (status=2, category=cwd-failed)\n"},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/pwd","--cwd","relative"],"status":1,"stdout":"","stderr":"error: managed exec working directory must be an absolute path of at most 4096 bytes\n"},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/echo","--outcome-report","{root}/reports/exec.json"],"status":1,"stdout":"","stderr":"error: outcome report already exists: {root}/reports/exec.json\n"},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/echo","--outcome-report","{root}/missing/exec.json"],"status":1,"stdout":"","stderr":"error: outcome report parent is not a plain directory: {root}/missing\n"},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, -{"name":"lifecycle: one share","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 2,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 2,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root}/distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root}/scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount\",\"/data,{root}/share,ro\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/pwd","--cwd","/data"],"status":0,"stdout":"/data\n","stderr":""},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 2,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 2,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 2,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, -{"name":"lifecycle: caller-owned share","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share,rw","--mount-owner","caller"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 3,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"caller\"\n },\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 3,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"caller\"\n },\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root}/distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root}/scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount\",\"/data,{root}/share,rw\",\"--mount-owner\",\"caller\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 3,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"caller\"\n },\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 3,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"caller\"\n },\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 3,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"caller\"\n },\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, -{"name":"lifecycle: several shares","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share","--mount","/other,{root}/share2,rw"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 4,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/other\",\n \"host_path\": \"{root}/share2\",\n \"owner\": \"vmm\"\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 4,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/other\",\n \"host_path\": \"{root}/share2\",\n \"owner\": \"vmm\"\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root}/distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root}/scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount-aggregate\",\"/run/nvx/shares\",\"--mount-child\",\"0-bd47413b5c03dfc6e4b5b8143fe205ea,{root}/share,ro\",\"--mount-child\",\"1-bf2faee2596026620b5cbc997c2d7d45,{root}/share2,rw\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox nvx_share=0-bd47413b5c03dfc6e4b5b8143fe205ea,/data,ro nvx_share=1-bf2faee2596026620b5cbc997c2d7d45,/other,rw\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/pwd","--cwd","/other"],"status":0,"stdout":"/other\n","stderr":""},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 4,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/other\",\n \"host_path\": \"{root}/share2\",\n \"owner\": \"vmm\"\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 4,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/other\",\n \"host_path\": \"{root}/share2\",\n \"owner\": \"vmm\"\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 4,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/other\",\n \"host_path\": \"{root}/share2\",\n \"owner\": \"vmm\"\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, -{"name":"lifecycle: share policies","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share,rw","--mount-deny","{root}/share/hidden","--mount-allow","{root}/share/hidden/shown","--mount-write","{root}/share/writable"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 5,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"rw\",\n \"allowed_paths\": [\n \"{root}/share/hidden/shown\"\n ],\n \"denied_paths\": [\n \"{root}/share/hidden\"\n ],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\",\n \"writable_paths\": [\n \"{root}/share/writable\"\n ]\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 5,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"rw\",\n \"allowed_paths\": [\n \"{root}/share/hidden/shown\"\n ],\n \"denied_paths\": [\n \"{root}/share/hidden\"\n ],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\",\n \"writable_paths\": [\n \"{root}/share/writable\"\n ]\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root}/distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root}/scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount\",\"/data,{root}/share,rw\",\"--mount-deny\",\"{root}/share/hidden\",\"--mount-allow\",\"{root}/share/hidden/shown\",\"--mount-write\",\"{root}/share/writable\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 5,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"rw\",\n \"allowed_paths\": [\n \"{root}/share/hidden/shown\"\n ],\n \"denied_paths\": [\n \"{root}/share/hidden\"\n ],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\",\n \"writable_paths\": [\n \"{root}/share/writable\"\n ]\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 5,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"rw\",\n \"allowed_paths\": [\n \"{root}/share/hidden/shown\"\n ],\n \"denied_paths\": [\n \"{root}/share/hidden\"\n ],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\",\n \"writable_paths\": [\n \"{root}/share/writable\"\n ]\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 5,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"rw\",\n \"allowed_paths\": [\n \"{root}/share/hidden/shown\"\n ],\n \"denied_paths\": [\n \"{root}/share/hidden\"\n ],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\",\n \"writable_paths\": [\n \"{root}/share/writable\"\n ]\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, -{"name":"lifecycle: file share","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/etc/single.txt,{root}/single.txt,ro"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 6,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"allowed_paths\": [],\n \"denied_paths\": [],\n \"guest_target\": \"/etc/single.txt\",\n \"host_path\": \"{root}/single.txt\",\n \"kind\": \"file\",\n \"owner\": \"vmm\",\n \"writable_paths\": []\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 6,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"allowed_paths\": [],\n \"denied_paths\": [],\n \"guest_target\": \"/etc/single.txt\",\n \"host_path\": \"{root}/single.txt\",\n \"kind\": \"file\",\n \"owner\": \"vmm\",\n \"writable_paths\": []\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root}/distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root}/scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount-aggregate\",\"/run/nvx/shares\",\"--mount-child\",\"0-a80a05e9cb129bc27b7ba103bf2f5ac0,{root}/single.txt,ro,file\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox nvx_share=0-a80a05e9cb129bc27b7ba103bf2f5ac0,/etc/single.txt,ro,file\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 6,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"allowed_paths\": [],\n \"denied_paths\": [],\n \"guest_target\": \"/etc/single.txt\",\n \"host_path\": \"{root}/single.txt\",\n \"kind\": \"file\",\n \"owner\": \"vmm\",\n \"writable_paths\": []\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 6,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"allowed_paths\": [],\n \"denied_paths\": [],\n \"guest_target\": \"/etc/single.txt\",\n \"host_path\": \"{root}/single.txt\",\n \"kind\": \"file\",\n \"owner\": \"vmm\",\n \"writable_paths\": []\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 6,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"allowed_paths\": [],\n \"denied_paths\": [],\n \"guest_target\": \"/etc/single.txt\",\n \"host_path\": \"{root}/single.txt\",\n \"kind\": \"file\",\n \"owner\": \"vmm\",\n \"writable_paths\": []\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, -{"name":"lifecycle: network and launch options","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/server","--arg=--serve","--hostname","worker","--workload-user","1000:1000","--memory-max","268435456","--pids-max","64","--memory-mib","384","--hypervisor","auto","--cmdline","quiet","--net","10.0.0.2/24","--network-profile","portable","--network-egress","allow","--network-ingress","deny","--network-egress-policy-file","{root}/policy.json","--host-loopback","allow","--host-loopback-forward","tcp:18080:8080","--host-loopback-forward","udp:19090:9090"],"write":{"policy.json":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 443}], \"deny\": [{\"cidr\": \"192.0.2.128/25\"}]}"},"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"],"mode":448},"state/config.json":{"mode":384,"text":"{\n \"cmdline\": \"quiet\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"worker\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": 268435456,\n \"memory_mib\": 384,\n \"mount\": null,\n \"network\": {\n \"allow\": {\n \"name\": \"--network-egress-allow\",\n \"rules\": [\n {\n \"ports\": [\n {\n \"endPort\": null,\n \"port\": 443,\n \"protocol\": \"tcp\"\n }\n ],\n \"to\": [\n {\n \"cidr\": \"192.0.2.0/24\",\n \"except\": []\n }\n ]\n }\n ]\n },\n \"deny\": {\n \"name\": \"--network-egress-deny\",\n \"rules\": [\n {\n \"ports\": [],\n \"to\": [\n {\n \"cidr\": \"192.0.2.128/25\",\n \"except\": []\n }\n ]\n }\n ]\n },\n \"egress\": \"allow\",\n \"guestNetwork\": \"10.0.0.2/24\",\n \"hostLoopback\": \"allow\",\n \"hostLoopbackForwards\": [\n {\n \"guestPort\": 8080,\n \"hostPort\": 18080,\n \"protocol\": \"tcp\"\n },\n {\n \"guestPort\": 9090,\n \"hostPort\": 19090,\n \"protocol\": \"udp\"\n }\n ],\n \"ingress\": \"deny\",\n \"proxy\": null\n },\n \"pids_max\": 64,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 1000,\n \"workload_uid\": 1000\n}\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"mode":384,"text":"{\n \"cmdline\": \"quiet\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"worker\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": 268435456,\n \"memory_mib\": 384,\n \"mount\": null,\n \"network\": {\n \"allow\": {\n \"name\": \"--network-egress-allow\",\n \"rules\": [\n {\n \"ports\": [\n {\n \"endPort\": null,\n \"port\": 443,\n \"protocol\": \"tcp\"\n }\n ],\n \"to\": [\n {\n \"cidr\": \"192.0.2.0/24\",\n \"except\": []\n }\n ]\n }\n ]\n },\n \"deny\": {\n \"name\": \"--network-egress-deny\",\n \"rules\": [\n {\n \"ports\": [],\n \"to\": [\n {\n \"cidr\": \"192.0.2.128/25\",\n \"except\": []\n }\n ]\n }\n ]\n },\n \"egress\": \"allow\",\n \"guestNetwork\": \"10.0.0.2/24\",\n \"hostLoopback\": \"allow\",\n \"hostLoopbackForwards\": [\n {\n \"guestPort\": 8080,\n \"hostPort\": 18080,\n \"protocol\": \"tcp\"\n },\n {\n \"guestPort\": 9090,\n \"hostPort\": 19090,\n \"protocol\": \"udp\"\n }\n ],\n \"ingress\": \"deny\",\n \"proxy\": null\n },\n \"pids_max\": 64,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 1000,\n \"workload_uid\": 1000\n}\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root}/distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root}/scratch.ext4\",\"--microvm-workload-identity\",\"1000:1000\",\"--net\",\"10.0.0.2/24\",\"--network-profile\",\"portable\",\"--network-egress\",\"allow\",\"--network-ingress\",\"deny\",\"--host-loopback\",\"allow\",\"--network-egress-allow\",\"192.0.2.0/24:tcp:443\",\"--network-egress-deny\",\"192.0.2.128/25\",\"--host-loopback-forward\",\"tcp:18080:8080\",\"--host-loopback-forward\",\"udp:19090:9090\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"384M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"quiet nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=worker nvx_memory_max=268435456 nvx_pids_max=65\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"mode":384,"text":"{\n \"cmdline\": \"quiet\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"worker\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": 268435456,\n \"memory_mib\": 384,\n \"mount\": null,\n \"network\": {\n \"allow\": {\n \"name\": \"--network-egress-allow\",\n \"rules\": [\n {\n \"ports\": [\n {\n \"endPort\": null,\n \"port\": 443,\n \"protocol\": \"tcp\"\n }\n ],\n \"to\": [\n {\n \"cidr\": \"192.0.2.0/24\",\n \"except\": []\n }\n ]\n }\n ]\n },\n \"deny\": {\n \"name\": \"--network-egress-deny\",\n \"rules\": [\n {\n \"ports\": [],\n \"to\": [\n {\n \"cidr\": \"192.0.2.128/25\",\n \"except\": []\n }\n ]\n }\n ]\n },\n \"egress\": \"allow\",\n \"guestNetwork\": \"10.0.0.2/24\",\n \"hostLoopback\": \"allow\",\n \"hostLoopbackForwards\": [\n {\n \"guestPort\": 8080,\n \"hostPort\": 18080,\n \"protocol\": \"tcp\"\n },\n {\n \"guestPort\": 9090,\n \"hostPort\": 19090,\n \"protocol\": \"udp\"\n }\n ],\n \"ingress\": \"deny\",\n \"proxy\": null\n },\n \"pids_max\": 64,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 1000,\n \"workload_uid\": 1000\n}\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 1,\n \"deny_rule_count\": 1,\n \"host_loopback\": \"allow\",\n \"mode\": \"rules\",\n \"status\": \"applied\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"mode":384,"text":"{\n \"cmdline\": \"quiet\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"worker\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": 268435456,\n \"memory_mib\": 384,\n \"mount\": null,\n \"network\": {\n \"allow\": {\n \"name\": \"--network-egress-allow\",\n \"rules\": [\n {\n \"ports\": [\n {\n \"endPort\": null,\n \"port\": 443,\n \"protocol\": \"tcp\"\n }\n ],\n \"to\": [\n {\n \"cidr\": \"192.0.2.0/24\",\n \"except\": []\n }\n ]\n }\n ]\n },\n \"deny\": {\n \"name\": \"--network-egress-deny\",\n \"rules\": [\n {\n \"ports\": [],\n \"to\": [\n {\n \"cidr\": \"192.0.2.128/25\",\n \"except\": []\n }\n ]\n }\n ]\n },\n \"egress\": \"allow\",\n \"guestNetwork\": \"10.0.0.2/24\",\n \"hostLoopback\": \"allow\",\n \"hostLoopbackForwards\": [\n {\n \"guestPort\": 8080,\n \"hostPort\": 18080,\n \"protocol\": \"tcp\"\n },\n {\n \"guestPort\": 9090,\n \"hostPort\": 19090,\n \"protocol\": \"udp\"\n }\n ],\n \"ingress\": \"deny\",\n \"proxy\": null\n },\n \"pids_max\": 64,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 1000,\n \"workload_uid\": 1000\n}\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"mode":384,"text":"{\n \"cmdline\": \"quiet\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"worker\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": 268435456,\n \"memory_mib\": 384,\n \"mount\": null,\n \"network\": {\n \"allow\": {\n \"name\": \"--network-egress-allow\",\n \"rules\": [\n {\n \"ports\": [\n {\n \"endPort\": null,\n \"port\": 443,\n \"protocol\": \"tcp\"\n }\n ],\n \"to\": [\n {\n \"cidr\": \"192.0.2.0/24\",\n \"except\": []\n }\n ]\n }\n ]\n },\n \"deny\": {\n \"name\": \"--network-egress-deny\",\n \"rules\": [\n {\n \"ports\": [],\n \"to\": [\n {\n \"cidr\": \"192.0.2.128/25\",\n \"except\": []\n }\n ]\n }\n ]\n },\n \"egress\": \"allow\",\n \"guestNetwork\": \"10.0.0.2/24\",\n \"hostLoopback\": \"allow\",\n \"hostLoopbackForwards\": [\n {\n \"guestPort\": 8080,\n \"hostPort\": 18080,\n \"protocol\": \"tcp\"\n },\n {\n \"guestPort\": 9090,\n \"hostPort\": 19090,\n \"protocol\": \"udp\"\n }\n ],\n \"ingress\": \"deny\",\n \"proxy\": null\n },\n \"pids_max\": 64,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 1000,\n \"workload_uid\": 1000\n}\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 1,\n \"deny_rule_count\": 1,\n \"host_loopback\": \"allow\",\n \"mode\": \"rules\",\n \"status\": \"applied\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, -{"name":"lifecycle: proxy-only network","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--net","10.0.0.2/24","--network-profile","portable","--network-egress","deny","--host-loopback","deny","--network-proxy","10.0.0.1:3128"],"check":["state/config.json"],"status":0,"stdout":"","stderr":"","files":{"state/config.json":{"mode":384,"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"network\": {\n \"allow\": {\n \"name\": \"--network-egress-allow\",\n \"rules\": []\n },\n \"deny\": {\n \"name\": \"--network-egress-deny\",\n \"rules\": []\n },\n \"egress\": \"deny\",\n \"guestNetwork\": \"10.0.0.2/24\",\n \"hostLoopback\": \"deny\",\n \"hostLoopbackForwards\": [],\n \"ingress\": \"deny\",\n \"proxy\": {\n \"port\": 3128,\n \"scheme\": null\n }\n },\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n"}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root}/distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root}/scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--net\",\"10.0.0.2/24\",\"--network-profile\",\"portable\",\"--network-egress\",\"deny\",\"--network-ingress\",\"deny\",\"--host-loopback\",\"deny\",\"--network-proxy\",\"10.0.0.1:3128\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}]}, +{"name":"lifecycle: minimal","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:/proc/self/fd/{int},ro\",\"--microvm-sandbox-block\",\"scratch:file:/proc/self/fd/{int}\",\"--microvm-workload-identity\",\"65534:65534\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/echo","--arg","hello","--arg","world"],"status":0,"stdout":"hello world\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/pwd","--cwd","/tmp"],"status":0,"stdout":"/tmp\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/sh","--arg=-c","--arg","echo out; echoerr err; exit 3","--outcome-report","{root}/reports/exec.json"],"check":["reports/exec.json"],"status":3,"stdout":"out\n","stderr":"err\n","files":{"reports/exec.json":{"text":"{\n \"operation_id\": \"{hex32}\",\n \"outcome\": {\n \"category\": \"exit\",\n \"operation\": \"exec\",\n \"status_code\": 3\n },\n \"schema_version\": 1\n}\n","mode":384}}},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/usr/bin/env","--environment","A=1","--environment","B=two"],"status":0,"stdout":"A=1\nB=two\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/usr/bin/printenv","--arg","PATH","--inherit-default-environment","--environment","A=1"],"status":0,"stdout":"/usr/sbin:/usr/bin:/sbin:/bin\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/usr/bin/env","--environment-file","{root}/env.json"],"write":{"env.json":"[\"X=1\", \"Y=two words\"]"},"status":0,"stdout":"X=1\nY=two words\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/usr/bin/env","--environment-file","{root}/empty.json"],"write":{"empty.json":"[]"},"status":0,"stdout":"","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/sh","--arg=-c","--arg","sleep 5000","--exec-timeout-ms","50","--outcome-report","{root}/reports/exec-timeout.json"],"check":["reports/exec-timeout.json"],"status":124,"stdout":"","stderr":"","files":{"reports/exec-timeout.json":{"text":"{\n \"operation_id\": \"{hex32}\",\n \"outcome\": {\n \"category\": \"timeout\",\n \"operation\": \"exec\",\n \"status_code\": 124\n },\n \"schema_version\": 1\n}\n","mode":384}}},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/sh","--arg=-c","--arg","signal 9"],"status":137,"stdout":"","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/missing"],"status":127,"stdout":"","stderr":"openvmm: /bin/missing: not found\n"},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/pwd","--cwd","/missing"],"status":1,"stdout":"","stderr":"error: managed guest rejected exec (status=2, category=cwd-failed)\n"},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/pwd","--cwd","relative"],"status":1,"stdout":"","stderr":"error: managed exec working directory must be an absolute path of at most 4096 bytes\n"},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/echo","--outcome-report","{root}/reports/exec.json"],"status":1,"stdout":"","stderr":"error: outcome report already exists: {root}/reports/exec.json\n"},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/echo","--outcome-report","{root}/missing/exec.json"],"status":1,"stdout":"","stderr":"error: outcome report parent is not a plain directory: {root}/missing\n"},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: one share","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 2,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 2,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:/proc/self/fd/{int},ro\",\"--microvm-sandbox-block\",\"scratch:file:/proc/self/fd/{int}\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount\",\"/data,{root}/share,ro\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/pwd","--cwd","/data"],"status":0,"stdout":"/data\n","stderr":""},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 2,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 2,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 2,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: caller-owned share","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share,rw","--mount-owner","caller"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 3,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"caller\"\n },\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 3,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"caller\"\n },\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:/proc/self/fd/{int},ro\",\"--microvm-sandbox-block\",\"scratch:file:/proc/self/fd/{int}\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount\",\"/data,{root}/share,rw\",\"--mount-owner\",\"caller\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 3,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"caller\"\n },\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 3,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"caller\"\n },\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 3,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"caller\"\n },\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: several shares","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share","--mount","/other,{root}/share2,rw"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 4,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/other\",\n \"host_path\": \"{root}/share2\",\n \"owner\": \"vmm\"\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 4,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/other\",\n \"host_path\": \"{root}/share2\",\n \"owner\": \"vmm\"\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:/proc/self/fd/{int},ro\",\"--microvm-sandbox-block\",\"scratch:file:/proc/self/fd/{int}\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount-aggregate\",\"/run/nvx/shares\",\"--mount-child\",\"0-bd47413b5c03dfc6e4b5b8143fe205ea,{root}/share,ro\",\"--mount-child\",\"1-bf2faee2596026620b5cbc997c2d7d45,{root}/share2,rw\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox nvx_share=0-bd47413b5c03dfc6e4b5b8143fe205ea,/data,ro nvx_share=1-bf2faee2596026620b5cbc997c2d7d45,/other,rw\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","exec","--state-dir","{root}/state","--entrypoint","/bin/pwd","--cwd","/other"],"status":0,"stdout":"/other\n","stderr":""},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 4,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/other\",\n \"host_path\": \"{root}/share2\",\n \"owner\": \"vmm\"\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 4,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/other\",\n \"host_path\": \"{root}/share2\",\n \"owner\": \"vmm\"\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 4,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"denied_paths\": [],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\"\n },\n {\n \"access\": \"rw\",\n \"denied_paths\": [],\n \"guest_target\": \"/other\",\n \"host_path\": \"{root}/share2\",\n \"owner\": \"vmm\"\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: share policies","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/data,{root}/share,rw","--mount-deny","{root}/share/hidden","--mount-allow","{root}/share/hidden/shown","--mount-write","{root}/share/writable"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 5,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"rw\",\n \"allowed_paths\": [\n \"{root}/share/hidden/shown\"\n ],\n \"denied_paths\": [\n \"{root}/share/hidden\"\n ],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\",\n \"writable_paths\": [\n \"{root}/share/writable\"\n ]\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 5,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"rw\",\n \"allowed_paths\": [\n \"{root}/share/hidden/shown\"\n ],\n \"denied_paths\": [\n \"{root}/share/hidden\"\n ],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\",\n \"writable_paths\": [\n \"{root}/share/writable\"\n ]\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:/proc/self/fd/{int},ro\",\"--microvm-sandbox-block\",\"scratch:file:/proc/self/fd/{int}\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount\",\"/data,{root}/share,rw\",\"--mount-deny\",\"{root}/share/hidden\",\"--mount-allow\",\"{root}/share/hidden/shown\",\"--mount-write\",\"{root}/share/writable\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 5,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"rw\",\n \"allowed_paths\": [\n \"{root}/share/hidden/shown\"\n ],\n \"denied_paths\": [\n \"{root}/share/hidden\"\n ],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\",\n \"writable_paths\": [\n \"{root}/share/writable\"\n ]\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 5,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"rw\",\n \"allowed_paths\": [\n \"{root}/share/hidden/shown\"\n ],\n \"denied_paths\": [\n \"{root}/share/hidden\"\n ],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\",\n \"writable_paths\": [\n \"{root}/share/writable\"\n ]\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 5,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"rw\",\n \"allowed_paths\": [\n \"{root}/share/hidden/shown\"\n ],\n \"denied_paths\": [\n \"{root}/share/hidden\"\n ],\n \"guest_target\": \"/data\",\n \"host_path\": \"{root}/share\",\n \"owner\": \"vmm\",\n \"writable_paths\": [\n \"{root}/share/writable\"\n ]\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: file share","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--mount","/etc/single.txt,{root}/single.txt,ro"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 6,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"allowed_paths\": [],\n \"denied_paths\": [],\n \"guest_target\": \"/etc/single.txt\",\n \"host_path\": \"{root}/single.txt\",\n \"kind\": \"file\",\n \"owner\": \"vmm\",\n \"writable_paths\": []\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 6,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"allowed_paths\": [],\n \"denied_paths\": [],\n \"guest_target\": \"/etc/single.txt\",\n \"host_path\": \"{root}/single.txt\",\n \"kind\": \"file\",\n \"owner\": \"vmm\",\n \"writable_paths\": []\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:/proc/self/fd/{int},ro\",\"--microvm-sandbox-block\",\"scratch:file:/proc/self/fd/{int}\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount-aggregate\",\"/run/nvx/shares\",\"--mount-child\",\"0-a80a05e9cb129bc27b7ba103bf2f5ac0,{root}/single.txt,ro,file\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox nvx_share=0-a80a05e9cb129bc27b7ba103bf2f5ac0,/etc/single.txt,ro,file\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 6,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"allowed_paths\": [],\n \"denied_paths\": [],\n \"guest_target\": \"/etc/single.txt\",\n \"host_path\": \"{root}/single.txt\",\n \"kind\": \"file\",\n \"owner\": \"vmm\",\n \"writable_paths\": []\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 6,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"allowed_paths\": [],\n \"denied_paths\": [],\n \"guest_target\": \"/etc/single.txt\",\n \"host_path\": \"{root}/single.txt\",\n \"kind\": \"file\",\n \"owner\": \"vmm\",\n \"writable_paths\": []\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 6,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mounts\": [\n {\n \"access\": \"ro\",\n \"allowed_paths\": [],\n \"denied_paths\": [],\n \"guest_target\": \"/etc/single.txt\",\n \"host_path\": \"{root}/single.txt\",\n \"kind\": \"file\",\n \"owner\": \"vmm\",\n \"writable_paths\": []\n }\n ],\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: network and launch options","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--entrypoint","/bin/server","--arg=--serve","--hostname","worker","--workload-user","1000:1000","--memory-max","268435456","--pids-max","64","--memory-mib","384","--hypervisor","auto","--cmdline","quiet","--net","10.0.0.2/24","--network-profile","portable","--network-egress","allow","--network-ingress","deny","--network-egress-policy-file","{root}/policy.json","--host-loopback","allow","--host-loopback-forward","tcp:18080:8080","--host-loopback-forward","udp:19090:9090"],"write":{"policy.json":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 443}], \"deny\": [{\"cidr\": \"192.0.2.128/25\"}]}"},"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"],"mode":448},"state/config.json":{"mode":384,"text":"{\n \"cmdline\": \"quiet\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"worker\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": 268435456,\n \"memory_mib\": 384,\n \"mount\": null,\n \"network\": {\n \"allow\": {\n \"name\": \"--network-egress-allow\",\n \"rules\": [\n {\n \"ports\": [\n {\n \"endPort\": null,\n \"port\": 443,\n \"protocol\": \"tcp\"\n }\n ],\n \"to\": [\n {\n \"cidr\": \"192.0.2.0/24\",\n \"except\": []\n }\n ]\n }\n ]\n },\n \"deny\": {\n \"name\": \"--network-egress-deny\",\n \"rules\": [\n {\n \"ports\": [],\n \"to\": [\n {\n \"cidr\": \"192.0.2.128/25\",\n \"except\": []\n }\n ]\n }\n ]\n },\n \"egress\": \"allow\",\n \"guestNetwork\": \"10.0.0.2/24\",\n \"hostLoopback\": \"allow\",\n \"hostLoopbackForwards\": [\n {\n \"guestPort\": 8080,\n \"hostPort\": 18080,\n \"protocol\": \"tcp\"\n },\n {\n \"guestPort\": 9090,\n \"hostPort\": 19090,\n \"protocol\": \"udp\"\n }\n ],\n \"ingress\": \"deny\",\n \"proxy\": null\n },\n \"pids_max\": 64,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 1000,\n \"workload_uid\": 1000\n}\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"mode":384,"text":"{\n \"cmdline\": \"quiet\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"worker\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": 268435456,\n \"memory_mib\": 384,\n \"mount\": null,\n \"network\": {\n \"allow\": {\n \"name\": \"--network-egress-allow\",\n \"rules\": [\n {\n \"ports\": [\n {\n \"endPort\": null,\n \"port\": 443,\n \"protocol\": \"tcp\"\n }\n ],\n \"to\": [\n {\n \"cidr\": \"192.0.2.0/24\",\n \"except\": []\n }\n ]\n }\n ]\n },\n \"deny\": {\n \"name\": \"--network-egress-deny\",\n \"rules\": [\n {\n \"ports\": [],\n \"to\": [\n {\n \"cidr\": \"192.0.2.128/25\",\n \"except\": []\n }\n ]\n }\n ]\n },\n \"egress\": \"allow\",\n \"guestNetwork\": \"10.0.0.2/24\",\n \"hostLoopback\": \"allow\",\n \"hostLoopbackForwards\": [\n {\n \"guestPort\": 8080,\n \"hostPort\": 18080,\n \"protocol\": \"tcp\"\n },\n {\n \"guestPort\": 9090,\n \"hostPort\": 19090,\n \"protocol\": \"udp\"\n }\n ],\n \"ingress\": \"deny\",\n \"proxy\": null\n },\n \"pids_max\": 64,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 1000,\n \"workload_uid\": 1000\n}\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:/proc/self/fd/{int},ro\",\"--microvm-sandbox-block\",\"scratch:file:/proc/self/fd/{int}\",\"--microvm-workload-identity\",\"1000:1000\",\"--net\",\"10.0.0.2/24\",\"--network-profile\",\"portable\",\"--network-egress\",\"allow\",\"--network-ingress\",\"deny\",\"--host-loopback\",\"allow\",\"--network-egress-allow\",\"192.0.2.0/24:tcp:443\",\"--network-egress-deny\",\"192.0.2.128/25\",\"--host-loopback-forward\",\"tcp:18080:8080\",\"--host-loopback-forward\",\"udp:19090:9090\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"384M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"quiet nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=worker nvx_memory_max=268435456 nvx_pids_max=65\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"mode":384,"text":"{\n \"cmdline\": \"quiet\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"worker\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": 268435456,\n \"memory_mib\": 384,\n \"mount\": null,\n \"network\": {\n \"allow\": {\n \"name\": \"--network-egress-allow\",\n \"rules\": [\n {\n \"ports\": [\n {\n \"endPort\": null,\n \"port\": 443,\n \"protocol\": \"tcp\"\n }\n ],\n \"to\": [\n {\n \"cidr\": \"192.0.2.0/24\",\n \"except\": []\n }\n ]\n }\n ]\n },\n \"deny\": {\n \"name\": \"--network-egress-deny\",\n \"rules\": [\n {\n \"ports\": [],\n \"to\": [\n {\n \"cidr\": \"192.0.2.128/25\",\n \"except\": []\n }\n ]\n }\n ]\n },\n \"egress\": \"allow\",\n \"guestNetwork\": \"10.0.0.2/24\",\n \"hostLoopback\": \"allow\",\n \"hostLoopbackForwards\": [\n {\n \"guestPort\": 8080,\n \"hostPort\": 18080,\n \"protocol\": \"tcp\"\n },\n {\n \"guestPort\": 9090,\n \"hostPort\": 19090,\n \"protocol\": \"udp\"\n }\n ],\n \"ingress\": \"deny\",\n \"proxy\": null\n },\n \"pids_max\": 64,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 1000,\n \"workload_uid\": 1000\n}\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 1,\n \"deny_rule_count\": 1,\n \"host_loopback\": \"allow\",\n \"mode\": \"rules\",\n \"status\": \"applied\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","control.sock","openvmm.log","runtime.json","sandbox.lock"],"mode":448},"state/config.json":{"mode":384,"text":"{\n \"cmdline\": \"quiet\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"worker\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": 268435456,\n \"memory_mib\": 384,\n \"mount\": null,\n \"network\": {\n \"allow\": {\n \"name\": \"--network-egress-allow\",\n \"rules\": [\n {\n \"ports\": [\n {\n \"endPort\": null,\n \"port\": 443,\n \"protocol\": \"tcp\"\n }\n ],\n \"to\": [\n {\n \"cidr\": \"192.0.2.0/24\",\n \"except\": []\n }\n ]\n }\n ]\n },\n \"deny\": {\n \"name\": \"--network-egress-deny\",\n \"rules\": [\n {\n \"ports\": [],\n \"to\": [\n {\n \"cidr\": \"192.0.2.128/25\",\n \"except\": []\n }\n ]\n }\n ]\n },\n \"egress\": \"allow\",\n \"guestNetwork\": \"10.0.0.2/24\",\n \"hostLoopback\": \"allow\",\n \"hostLoopbackForwards\": [\n {\n \"guestPort\": 8080,\n \"hostPort\": 18080,\n \"protocol\": \"tcp\"\n },\n {\n \"guestPort\": 9090,\n \"hostPort\": 19090,\n \"protocol\": \"udp\"\n }\n ],\n \"ingress\": \"deny\",\n \"proxy\": null\n },\n \"pids_max\": 64,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 1000,\n \"workload_uid\": 1000\n}\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"{root}/state/control.sock\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n","mode":384},"state/control.capability":{"size":32,"mode":384},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"],"mode":448},"state/config.json":{"mode":384,"text":"{\n \"cmdline\": \"quiet\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"worker\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": 268435456,\n \"memory_mib\": 384,\n \"mount\": null,\n \"network\": {\n \"allow\": {\n \"name\": \"--network-egress-allow\",\n \"rules\": [\n {\n \"ports\": [\n {\n \"endPort\": null,\n \"port\": 443,\n \"protocol\": \"tcp\"\n }\n ],\n \"to\": [\n {\n \"cidr\": \"192.0.2.0/24\",\n \"except\": []\n }\n ]\n }\n ]\n },\n \"deny\": {\n \"name\": \"--network-egress-deny\",\n \"rules\": [\n {\n \"ports\": [],\n \"to\": [\n {\n \"cidr\": \"192.0.2.128/25\",\n \"except\": []\n }\n ]\n }\n ]\n },\n \"egress\": \"allow\",\n \"guestNetwork\": \"10.0.0.2/24\",\n \"hostLoopback\": \"allow\",\n \"hostLoopbackForwards\": [\n {\n \"guestPort\": 8080,\n \"hostPort\": 18080,\n \"protocol\": \"tcp\"\n },\n {\n \"guestPort\": 9090,\n \"hostPort\": 19090,\n \"protocol\": \"udp\"\n }\n ],\n \"ingress\": \"deny\",\n \"proxy\": null\n },\n \"pids_max\": 64,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 1000,\n \"workload_uid\": 1000\n}\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"kvm\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 1,\n \"deny_rule_count\": 1,\n \"host_loopback\": \"allow\",\n \"mode\": \"rules\",\n \"status\": \"applied\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: proxy-only network","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--net","10.0.0.2/24","--network-profile","portable","--network-egress","deny","--host-loopback","deny","--network-proxy","10.0.0.1:3128"],"check":["state/config.json"],"status":0,"stdout":"","stderr":"","files":{"state/config.json":{"mode":384,"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"network\": {\n \"allow\": {\n \"name\": \"--network-egress-allow\",\n \"rules\": []\n },\n \"deny\": {\n \"name\": \"--network-egress-deny\",\n \"rules\": []\n },\n \"egress\": \"deny\",\n \"guestNetwork\": \"10.0.0.2/24\",\n \"hostLoopback\": \"deny\",\n \"hostLoopbackForwards\": [],\n \"ingress\": \"deny\",\n \"proxy\": {\n \"port\": 3128,\n \"scheme\": null\n }\n },\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n"}}},{"argv":["sandbox","start","--state-dir","{root}/state"],"check":["build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:/proc/self/fd/{int},ro\",\"--microvm-sandbox-block\",\"scratch:file:/proc/self/fd/{int}\",\"--microvm-workload-identity\",\"65534:65534\",\"--net\",\"10.0.0.2/24\",\"--network-profile\",\"portable\",\"--network-egress\",\"deny\",\"--network-ingress\",\"deny\",\"--host-loopback\",\"deny\",\"--network-proxy\",\"10.0.0.1:3128\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"kvm\",\"--memory\",\"256M\",\"--kernel\",\"{root}/build/vmlinux\",\"--initrd\",\"{root}/build/initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen={root}/state/control.sock\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root}/state/outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}/state"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}]}, {"name":"state: operations without a sandbox","steps":[{"argv":["sandbox","start","--state-dir","{root}/state"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}/state\n"},{"argv":["sandbox","exec","--state-dir","{root}/state"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}/state\n"},{"argv":["sandbox","stop","--state-dir","{root}/state"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}/state\n"},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}/state\n"}]}, {"name":"state: refusals of a running sandbox","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"status":1,"stdout":"","stderr":"error: sandbox is already provisioned\n"},{"argv":["sandbox","start","--state-dir","{root}/state"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","start","--state-dir","{root}/state"],"status":1,"stdout":"","stderr":"error: sandbox is already running or has stale runtime state\n"},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"status":1,"stdout":"","stderr":"error: sandbox must be stopped before deprovision\n"},{"argv":["sandbox","stop","--state-dir","{root}/state"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","stop","--state-dir","{root}/state"],"status":1,"stdout":"","stderr":"error: sandbox is not running\n"},{"argv":["sandbox","exec","--state-dir","{root}/state"],"status":1,"stdout":"","stderr":"error: sandbox is not running\n"},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, -{"name":"state: relative and nested state directory","steps":[{"argv":["sandbox","provision","--state-dir","nested/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"check":["nested/state","nested/state/config.json"],"status":0,"stdout":"","stderr":"","files":{"nested/state":{"entries":["config.json","sandbox.lock"],"mode":448},"nested/state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384}}},{"argv":["sandbox","deprovision","--state-dir","nested/state"],"check":["nested/state"],"status":0,"stdout":"","stderr":"","files":{"nested/state":null}}]}, -{"name":"state: failed start","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--cmdline","fake_exit_on_start=5"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","start","--state-dir","{root}/state","--timeout","0.5"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":1,"stdout":"","stderr":"error: OpenVMM exited during startup with status 5\n","files":{"state":{"entries":["config.json","openvmm.log","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"fake_exit_on_start=5\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":{"text":"openvmm: failing the launch as requested\n"}}},{"argv":["sandbox","start","--state-dir","{root}/state","--timeout","0.5"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":1,"stdout":"","stderr":"error: OpenVMM exited during startup with status 5\n","files":{"state":{"entries":["config.json","openvmm.log","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"fake_exit_on_start=5\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":{"text":"openvmm: failing the launch as requested\n"}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, -{"name":"state: start without OpenVMM","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","start","--state-dir","{root}/state"],"remove":["openvmm/target/release/openvmm{exe}"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":1,"stdout":"","stderr":"error: OpenVMM release binary not found: {root}/openvmm/target/release/openvmm\n","files":{"state":{"entries":["config.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"status":0,"stdout":"","stderr":""}]}, +{"name":"state: relative and nested state directory","steps":[{"argv":["sandbox","provision","--state-dir","nested/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"check":["nested/state","nested/state/config.json"],"status":0,"stdout":"","stderr":"","files":{"nested/state":{"entries":["config.json","sandbox.lock"],"mode":448},"nested/state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384}}},{"argv":["sandbox","deprovision","--state-dir","nested/state"],"check":["nested/state"],"status":0,"stdout":"","stderr":"","files":{"nested/state":null}}]}, +{"name":"state: failed start","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4","--cmdline","fake_exit_on_start=5"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","start","--state-dir","{root}/state","--timeout","0.5"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":1,"stdout":"","stderr":"error: OpenVMM exited during startup with status 5\n","files":{"state":{"entries":["config.json","openvmm.log","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"fake_exit_on_start=5\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":{"text":"openvmm: failing the launch as requested\n"}}},{"argv":["sandbox","start","--state-dir","{root}/state","--timeout","0.5"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":1,"stdout":"","stderr":"error: OpenVMM exited during startup with status 5\n","files":{"state":{"entries":["config.json","openvmm.log","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"fake_exit_on_start=5\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":{"text":"openvmm: failing the launch as requested\n"}}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"state: start without OpenVMM","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","start","--state-dir","{root}/state"],"remove":["openvmm/target/release/openvmm{exe}"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":1,"stdout":"","stderr":"error: OpenVMM release binary not found: {root}/openvmm/target/release/openvmm\n","files":{"state":{"entries":["config.json","sandbox.lock"],"mode":448},"state/config.json":{"text":"{\n \"cmdline\": \"\",\n \"distro_binding\": {\n \"format\": \"erofs\",\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\n \"image\": {\n \"changed\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 7,\n \"modified\": {\n \"nanoseconds\": {int},\n \"seconds\": {int}\n },\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"manifest\": null,\n \"policy\": {\n \"result\": \"accepted\",\n \"revision\": 1\n },\n \"version\": 1\n },\n \"format\": 1,\n \"hostname\": \"nvx-sandbox\",\n \"hypervisor\": \"kvm\",\n \"layers\": [\n {\n \"path\": \"{root}/distro.erofs\",\n \"role\": \"distro\",\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\n }\n ],\n \"memory_max\": null,\n \"memory_mib\": 256,\n \"mount\": null,\n \"network\": null,\n \"pids_max\": null,\n \"scratch\": \"{root}/scratch.ext4\",\n \"scratch_binding\": {\n \"device\": {int},\n \"fileId\": \"{hex32}\",\n \"kind\": \"regular\",\n \"length\": 8,\n \"scheme\": \"linux\",\n \"version\": 1\n },\n \"workload_gid\": 65534,\n \"workload_uid\": 65534\n}\n","mode":384},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"status":0,"stdout":"","stderr":""}]}, {"name":"state: malformed configuration","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","start","--state-dir","{root}/state"],"write":{"state/config.json":"{\"format\": 7}"},"status":1,"stdout":"","stderr":"error: sandbox configuration has an unsupported format: {root}/state/config.json\n"},{"argv":["sandbox","start","--state-dir","{root}/state"],"write":{"state/config.json":"[]"},"status":1,"stdout":"","stderr":"error: sandbox configuration has an unsupported format: {root}/state/config.json\n"},{"argv":["sandbox","start","--state-dir","{root}/state"],"write":{"state/config.json":"{\"format\": 1}"},"status":1,"stdout":"","stderr":"error: sandbox configuration is malformed\n"},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}]}, {"name":"state: not a directory","steps":[{"argv":["sandbox","start","--state-dir","{root}/single.txt"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}/single.txt\n"},{"argv":["sandbox","provision","--state-dir","{root}/single.txt","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"status":1,"stdout":"","stderr":"error: [Errno 17] File exists: '{root}/single.txt'\n"},{"argv":["sandbox","deprovision","--state-dir","{root}/single.txt"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}/single.txt\n"}]}, {"name":"state: foreign file","steps":[{"argv":["sandbox","provision","--state-dir","{root}/state","--layer","distro,{root}/distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}/scratch.ext4"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","deprovision","--state-dir","{root}/state"],"write":{"state/foreign.txt":"x"},"check":["state"],"status":1,"stdout":"","stderr":"error: sandbox state directory contains files not owned by NVX: foreign.txt\n","files":{"state":{"entries":["foreign.txt"],"mode":448}}}]} @@ -262,7 +262,7 @@ {"name":"validation: relative mount target","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","data,{root}\\share"],"status":1,"stdout":"","stderr":"error: invalid sandbox mount target 'data': expected an absolute non-root path without empty, dot, or parent components, whitespace, '\\', '=', or ','\r\n"}]}, {"name":"validation: missing mount host","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\missing"],"status":1,"stdout":"","stderr":"error: sandbox mount host path is not a plain directory: {root}\\missing\r\n"}]}, {"name":"validation: invalid mount mode","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share,rx"],"status":1,"stdout":"","stderr":"error: unsupported --mount mode 'rx'; choose ro or rw (if HOST_PATH contains commas, end the value with ,ro or ,rw)\r\n"}]}, -{"name":"validation: deny outside share","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share","--mount-deny","{root}\\share2"],"status":2,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --mount /data,{root}\\share,ro --mount-deny {root}\\share2 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\"\r\n","stderr":"openvmm: invalid --mount-deny {root}\\share2\n"}]}, +{"name":"validation: deny outside share","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share","--mount-deny","{root}\\share2"],"status":2,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:\\\\?\\{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:\\\\?\\{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --mount /data,{root}\\share,ro --mount-deny {root}\\share2 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\"\r\n","stderr":"openvmm: invalid --mount-deny {root}\\share2\n"}]}, {"name":"validation: allow outside denied path","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share","--mount-allow","{root}\\share\\hidden\\shown"],"status":1,"stdout":"","stderr":"error: --mount-allow exposes a path inside a --mount-deny path of the same share, which has none\r\n"}]}, {"name":"validation: write in read-only share","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share,ro","--mount-write","{root}\\share\\writable"],"status":1,"stdout":"","stderr":"error: --mount-write narrows the writes of a read-write share; --mount /data is read-only\r\n"}]}, {"name":"validation: reserved command-line token","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--cmdline","nvx_sandbox=0"],"status":1,"stdout":"","stderr":"error: nvx_sandbox is owned by the sandbox profile\r\n"}]}, @@ -282,29 +282,29 @@ {"name":"dry run: proxy-only network","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--net","10.0.0.2/24","--network-profile","portable","--network-egress","deny","--host-loopback","deny","--network-proxy","10.0.0.1:3128","--dry-run"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --net 10.0.0.2/24 --network-profile portable --network-egress deny --network-ingress deny --host-loopback deny --network-proxy 10.0.0.1:3128 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\"\r\n","stderr":""}]}, {"name":"dry run: egress policy file","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--net","10.0.0.2/24","--network-profile","portable","--network-egress","deny","--network-egress-policy-file","{root}\\policy.json","--dry-run"],"write":{"policy.json":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"except\": [\"192.0.2.128/25\"]}, {\"cidr\": \"2001:db8::/32\", \"protocol\": \"tcp\", \"port\": 443}], \"deny\": [{\"cidr\": \"198.51.100.0/24\", \"protocol\": \"udp\"}]}"},"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --net 10.0.0.2/24 --network-profile portable --network-egress deny --network-ingress deny --network-egress-allow 192.0.2.0/25 --network-egress-allow 2001:db8::/32:tcp:443 --network-egress-deny 198.51.100.0/24:udp --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\"\r\n","stderr":""}]}, {"name":"dry run: quoted arguments","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--cmdline","a=\"b c\" d","--dry-run"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"a=\\\"b c\\\" d nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\"\r\n","stderr":""}]}, -{"name":"run: echo","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/echo","--arg","hello","--arg","world"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world\"\r\nhello world\n","stderr":""}]}, -{"name":"run: default operation","steps":[{"argv":["sandbox","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/echo","--arg","hello","--arg","world"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world\"\r\nhello world\n","stderr":""}]}, -{"name":"run: failure","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/false"],"status":1,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/false nvx_hostname=nvx-sandbox\"\r\n","stderr":""}]}, -{"name":"run: missing program","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/missing"],"status":127,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/missing nvx_hostname=nvx-sandbox\"\r\n","stderr":"openvmm: /bin/missing: not found\n"}]}, -{"name":"run: exit status","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/true","--cmdline","fake_exit_status=7"],"status":7,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"fake_exit_status=7 nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/true nvx_hostname=nvx-sandbox\"\r\n","stderr":""}]}, -{"name":"run: outcome report","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/echo","--arg","hello","--arg","world","--outcome-report","{root}\\reports\\openvmm-run.json"],"check":["reports/openvmm-run.json","build/fake-openvmm-reports.json"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world\" --microvm-report {root}\\reports\\openvmm-run.json\r\nhello world\n","stderr":"","files":{"reports/openvmm-run.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"run\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"build/fake-openvmm-reports.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--microvm-lifecycle\",\"one-shot\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"256M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world\",\"--microvm-report\",\"{root_json}\\\\reports\\\\openvmm-run.json\"]"}}}]}, -{"name":"run: network outcome report","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/true","--net","10.0.0.2/24","--network-profile","portable","--network-egress","deny","--network-egress-allow","10.0.0.0/8:tcp:443","--outcome-report","{root}\\reports\\openvmm-run.json"],"check":["reports/openvmm-run.json"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --net 10.0.0.2/24 --network-profile portable --network-egress deny --network-ingress deny --network-egress-allow 10.0.0.0/8:tcp:443 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/true nvx_hostname=nvx-sandbox\" --microvm-report {root}\\reports\\openvmm-run.json\r\n","stderr":"","files":{"reports/openvmm-run.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 1,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"allow\",\n \"mode\": \"rules\",\n \"status\": \"applied\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"run\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"}}}]}, +{"name":"run: echo","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/echo","--arg","hello","--arg","world"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:\\\\?\\{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:\\\\?\\{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world\"\r\nhello world\n","stderr":""}]}, +{"name":"run: default operation","steps":[{"argv":["sandbox","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/echo","--arg","hello","--arg","world"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:\\\\?\\{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:\\\\?\\{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world\"\r\nhello world\n","stderr":""}]}, +{"name":"run: failure","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/false"],"status":1,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:\\\\?\\{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:\\\\?\\{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/false nvx_hostname=nvx-sandbox\"\r\n","stderr":""}]}, +{"name":"run: missing program","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/missing"],"status":127,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:\\\\?\\{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:\\\\?\\{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/missing nvx_hostname=nvx-sandbox\"\r\n","stderr":"openvmm: /bin/missing: not found\n"}]}, +{"name":"run: exit status","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/true","--cmdline","fake_exit_status=7"],"status":7,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:\\\\?\\{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:\\\\?\\{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"fake_exit_status=7 nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/true nvx_hostname=nvx-sandbox\"\r\n","stderr":""}]}, +{"name":"run: outcome report","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/echo","--arg","hello","--arg","world","--outcome-report","{root}\\reports\\openvmm-run.json"],"check":["reports/openvmm-run.json","build/fake-openvmm-reports.json"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:\\\\?\\{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:\\\\?\\{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world\" --microvm-report {root}\\reports\\openvmm-run.json\r\nhello world\n","stderr":"","files":{"reports/openvmm-run.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"run\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"build/fake-openvmm-reports.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:\\\\\\\\?\\\\{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:\\\\\\\\?\\\\{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--microvm-lifecycle\",\"one-shot\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"256M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/echo nvx_hostname=nvx-sandbox nvx_arg=hello nvx_arg=world\",\"--microvm-report\",\"{root_json}\\\\reports\\\\openvmm-run.json\"]"}}}]}, +{"name":"run: network outcome report","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/true","--net","10.0.0.2/24","--network-profile","portable","--network-egress","deny","--network-egress-allow","10.0.0.0/8:tcp:443","--outcome-report","{root}\\reports\\openvmm-run.json"],"check":["reports/openvmm-run.json"],"status":0,"stdout":">> {root}\\openvmm\\target\\release\\openvmm.exe --machine microvm --microvm-sandbox-block distro:file:\\\\?\\{root}\\distro.erofs,ro --microvm-sandbox-block scratch:file:\\\\?\\{root}\\scratch.ext4 --microvm-workload-identity 65534:65534 --net 10.0.0.2/24 --network-profile portable --network-egress deny --network-ingress deny --network-egress-allow 10.0.0.0/8:tcp:443 --microvm-lifecycle one-shot --single-process --hypervisor whp --memory 256M --kernel {root}\\build\\vmlinux --initrd {root}\\build\\initramfs.cpio.gz --cmdline \"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/true nvx_hostname=nvx-sandbox\" --microvm-report {root}\\reports\\openvmm-run.json\r\n","stderr":"","files":{"reports/openvmm-run.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 1,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"allow\",\n \"mode\": \"rules\",\n \"status\": \"applied\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"run\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"}}}]}, {"name":"run: network options without --net","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--host-loopback-forward","8080"],"status":1,"stdout":"","stderr":"error: network policy options require --net\r\n"}]}, {"name":"run: missing OpenVMM","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"remove":["openvmm/target/release/openvmm{exe}"],"status":1,"stdout":"","stderr":"error: OpenVMM release binary not found: {root}\\openvmm\\target\\release\\openvmm.exe\r\n"}]}, {"name":"run: missing kernel","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"remove":["build/vmlinux"],"status":1,"stdout":"","stderr":"error: Linux direct kernel not found: {root}\\build\\vmlinux\r\n"}]}, {"name":"run: missing initramfs","steps":[{"argv":["sandbox","run","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"remove":["build/initramfs.cpio.gz"],"status":1,"stdout":"","stderr":"error: initramfs not found: {root}\\build\\initramfs.cpio.gz\r\n"}]}, -{"name":"lifecycle: minimal","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"256M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen=//./pipe/openvmm-microvm-{hex32}\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root_json}\\\\state\\\\outcome.json\"]"}}},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/echo","--arg","hello","--arg","world"],"status":0,"stdout":"hello world\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/pwd","--cwd","/tmp"],"status":0,"stdout":"/tmp\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/sh","--arg=-c","--arg","echo out; echoerr err; exit 3","--outcome-report","{root}\\reports\\exec.json"],"check":["reports/exec.json"],"status":3,"stdout":"out\n","stderr":"err\n","files":{"reports/exec.json":{"text":"{\n \"operation_id\": \"{hex32}\",\n \"outcome\": {\n \"category\": \"exit\",\n \"operation\": \"exec\",\n \"status_code\": 3\n },\n \"schema_version\": 1\n}\n"}}},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/usr/bin/env","--environment","A=1","--environment","B=two"],"status":0,"stdout":"A=1\nB=two\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/usr/bin/printenv","--arg","PATH","--inherit-default-environment","--environment","A=1"],"status":0,"stdout":"/usr/sbin:/usr/bin:/sbin:/bin\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/usr/bin/env","--environment-file","{root}\\env.json"],"write":{"env.json":"[\"X=1\", \"Y=two words\"]"},"status":0,"stdout":"X=1\nY=two words\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/usr/bin/env","--environment-file","{root}\\empty.json"],"write":{"empty.json":"[]"},"status":0,"stdout":"","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/sh","--arg=-c","--arg","sleep 5000","--exec-timeout-ms","50","--outcome-report","{root}\\reports\\exec-timeout.json"],"check":["reports/exec-timeout.json"],"status":124,"stdout":"","stderr":"","files":{"reports/exec-timeout.json":{"text":"{\n \"operation_id\": \"{hex32}\",\n \"outcome\": {\n \"category\": \"timeout\",\n \"operation\": \"exec\",\n \"status_code\": 124\n },\n \"schema_version\": 1\n}\n"}}},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/sh","--arg=-c","--arg","signal 9"],"status":137,"stdout":"","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/missing"],"status":127,"stdout":"","stderr":"openvmm: /bin/missing: not found\n"},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/pwd","--cwd","/missing"],"status":1,"stdout":"","stderr":"error: managed guest rejected exec (status=2, category=cwd-failed)\r\n"},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/pwd","--cwd","relative"],"status":1,"stdout":"","stderr":"error: managed exec working directory must be an absolute path of at most 4096 bytes\r\n"},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/echo","--outcome-report","{root}\\reports\\exec.json"],"status":1,"stdout":"","stderr":"error: outcome report already exists: {root}\\reports\\exec.json\r\n"},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/echo","--outcome-report","{root}\\missing\\exec.json"],"status":1,"stdout":"","stderr":"error: outcome report parent is not a plain directory: {root}\\missing\r\n"},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, -{"name":"lifecycle: one share","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 2,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 2,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount\",\"/data,{root_json}\\\\share,ro\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"256M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen=//./pipe/openvmm-microvm-{hex32}\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root_json}\\\\state\\\\outcome.json\"]"}}},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/pwd","--cwd","/data"],"status":0,"stdout":"/data\n","stderr":""},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 2,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 2,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 2,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, -{"name":"lifecycle: several shares","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share","--mount","/other,{root}\\share2,rw"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 4,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n {\r\n \"access\": \"rw\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/other\",\r\n \"host_path\": \"{root_json}\\\\share2\",\r\n \"owner\": \"vmm\"\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 4,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n {\r\n \"access\": \"rw\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/other\",\r\n \"host_path\": \"{root_json}\\\\share2\",\r\n \"owner\": \"vmm\"\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount-aggregate\",\"/run/nvx/shares\",\"--mount-child\",\"0-bd47413b5c03dfc6e4b5b8143fe205ea,{root_json}\\\\share,ro\",\"--mount-child\",\"1-bf2faee2596026620b5cbc997c2d7d45,{root_json}\\\\share2,rw\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"256M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox nvx_share=0-bd47413b5c03dfc6e4b5b8143fe205ea,/data,ro nvx_share=1-bf2faee2596026620b5cbc997c2d7d45,/other,rw\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen=//./pipe/openvmm-microvm-{hex32}\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root_json}\\\\state\\\\outcome.json\"]"}}},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/pwd","--cwd","/other"],"status":0,"stdout":"/other\n","stderr":""},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 4,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n {\r\n \"access\": \"rw\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/other\",\r\n \"host_path\": \"{root_json}\\\\share2\",\r\n \"owner\": \"vmm\"\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 4,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n {\r\n \"access\": \"rw\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/other\",\r\n \"host_path\": \"{root_json}\\\\share2\",\r\n \"owner\": \"vmm\"\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 4,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n {\r\n \"access\": \"rw\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/other\",\r\n \"host_path\": \"{root_json}\\\\share2\",\r\n \"owner\": \"vmm\"\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, -{"name":"lifecycle: share policies","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share,rw","--mount-deny","{root}\\share\\hidden","--mount-allow","{root}\\share\\hidden\\shown","--mount-write","{root}\\share\\writable"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 5,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"rw\",\r\n \"allowed_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\\\\shown\"\r\n ],\r\n \"denied_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\"\r\n ],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": [\r\n \"{root_json}\\\\share\\\\writable\"\r\n ]\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 5,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"rw\",\r\n \"allowed_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\\\\shown\"\r\n ],\r\n \"denied_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\"\r\n ],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": [\r\n \"{root_json}\\\\share\\\\writable\"\r\n ]\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount\",\"/data,{root_json}\\\\share,rw\",\"--mount-deny\",\"{root_json}\\\\share\\\\hidden\",\"--mount-allow\",\"{root_json}\\\\share\\\\hidden\\\\shown\",\"--mount-write\",\"{root_json}\\\\share\\\\writable\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"256M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen=//./pipe/openvmm-microvm-{hex32}\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root_json}\\\\state\\\\outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 5,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"rw\",\r\n \"allowed_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\\\\shown\"\r\n ],\r\n \"denied_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\"\r\n ],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": [\r\n \"{root_json}\\\\share\\\\writable\"\r\n ]\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 5,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"rw\",\r\n \"allowed_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\\\\shown\"\r\n ],\r\n \"denied_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\"\r\n ],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": [\r\n \"{root_json}\\\\share\\\\writable\"\r\n ]\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 5,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"rw\",\r\n \"allowed_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\\\\shown\"\r\n ],\r\n \"denied_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\"\r\n ],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": [\r\n \"{root_json}\\\\share\\\\writable\"\r\n ]\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, -{"name":"lifecycle: file share","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/etc/single.txt,{root}\\single.txt,ro"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 6,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"allowed_paths\": [],\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/etc/single.txt\",\r\n \"host_path\": \"{root_json}\\\\single.txt\",\r\n \"kind\": \"file\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": []\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 6,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"allowed_paths\": [],\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/etc/single.txt\",\r\n \"host_path\": \"{root_json}\\\\single.txt\",\r\n \"kind\": \"file\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": []\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount-aggregate\",\"/run/nvx/shares\",\"--mount-child\",\"0-a80a05e9cb129bc27b7ba103bf2f5ac0,{root_json}\\\\single.txt,ro,file\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"256M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox nvx_share=0-a80a05e9cb129bc27b7ba103bf2f5ac0,/etc/single.txt,ro,file\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen=//./pipe/openvmm-microvm-{hex32}\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root_json}\\\\state\\\\outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 6,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"allowed_paths\": [],\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/etc/single.txt\",\r\n \"host_path\": \"{root_json}\\\\single.txt\",\r\n \"kind\": \"file\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": []\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 6,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"allowed_paths\": [],\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/etc/single.txt\",\r\n \"host_path\": \"{root_json}\\\\single.txt\",\r\n \"kind\": \"file\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": []\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 6,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"allowed_paths\": [],\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/etc/single.txt\",\r\n \"host_path\": \"{root_json}\\\\single.txt\",\r\n \"kind\": \"file\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": []\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, -{"name":"lifecycle: network and launch options","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/server","--arg=--serve","--hostname","worker","--workload-user","1000:1000","--memory-max","268435456","--pids-max","64","--memory-mib","384","--hypervisor","auto","--cmdline","quiet","--net","10.0.0.2/24","--network-profile","portable","--network-egress","allow","--network-ingress","deny","--network-egress-policy-file","{root}\\policy.json","--host-loopback","allow","--host-loopback-forward","tcp:18080:8080","--host-loopback-forward","udp:19090:9090"],"write":{"policy.json":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 443}], \"deny\": [{\"cidr\": \"192.0.2.128/25\"}]}"},"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"quiet\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"worker\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": 268435456,\r\n \"memory_mib\": 384,\r\n \"mount\": null,\r\n \"network\": {\r\n \"allow\": {\r\n \"name\": \"--network-egress-allow\",\r\n \"rules\": [\r\n {\r\n \"ports\": [\r\n {\r\n \"endPort\": null,\r\n \"port\": 443,\r\n \"protocol\": \"tcp\"\r\n }\r\n ],\r\n \"to\": [\r\n {\r\n \"cidr\": \"192.0.2.0/24\",\r\n \"except\": []\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n \"deny\": {\r\n \"name\": \"--network-egress-deny\",\r\n \"rules\": [\r\n {\r\n \"ports\": [],\r\n \"to\": [\r\n {\r\n \"cidr\": \"192.0.2.128/25\",\r\n \"except\": []\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n \"egress\": \"allow\",\r\n \"guestNetwork\": \"10.0.0.2/24\",\r\n \"hostLoopback\": \"allow\",\r\n \"hostLoopbackForwards\": [\r\n {\r\n \"guestPort\": 8080,\r\n \"hostPort\": 18080,\r\n \"protocol\": \"tcp\"\r\n },\r\n {\r\n \"guestPort\": 9090,\r\n \"hostPort\": 19090,\r\n \"protocol\": \"udp\"\r\n }\r\n ],\r\n \"ingress\": \"deny\",\r\n \"proxy\": null\r\n },\r\n \"pids_max\": 64,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 1000,\r\n \"workload_uid\": 1000\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"quiet\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"worker\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": 268435456,\r\n \"memory_mib\": 384,\r\n \"mount\": null,\r\n \"network\": {\r\n \"allow\": {\r\n \"name\": \"--network-egress-allow\",\r\n \"rules\": [\r\n {\r\n \"ports\": [\r\n {\r\n \"endPort\": null,\r\n \"port\": 443,\r\n \"protocol\": \"tcp\"\r\n }\r\n ],\r\n \"to\": [\r\n {\r\n \"cidr\": \"192.0.2.0/24\",\r\n \"except\": []\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n \"deny\": {\r\n \"name\": \"--network-egress-deny\",\r\n \"rules\": [\r\n {\r\n \"ports\": [],\r\n \"to\": [\r\n {\r\n \"cidr\": \"192.0.2.128/25\",\r\n \"except\": []\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n \"egress\": \"allow\",\r\n \"guestNetwork\": \"10.0.0.2/24\",\r\n \"hostLoopback\": \"allow\",\r\n \"hostLoopbackForwards\": [\r\n {\r\n \"guestPort\": 8080,\r\n \"hostPort\": 18080,\r\n \"protocol\": \"tcp\"\r\n },\r\n {\r\n \"guestPort\": 9090,\r\n \"hostPort\": 19090,\r\n \"protocol\": \"udp\"\r\n }\r\n ],\r\n \"ingress\": \"deny\",\r\n \"proxy\": null\r\n },\r\n \"pids_max\": 64,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 1000,\r\n \"workload_uid\": 1000\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"1000:1000\",\"--net\",\"10.0.0.2/24\",\"--network-profile\",\"portable\",\"--network-egress\",\"allow\",\"--network-ingress\",\"deny\",\"--host-loopback\",\"allow\",\"--network-egress-allow\",\"192.0.2.0/24:tcp:443\",\"--network-egress-deny\",\"192.0.2.128/25\",\"--host-loopback-forward\",\"tcp:18080:8080\",\"--host-loopback-forward\",\"udp:19090:9090\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"384M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"quiet nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=worker nvx_memory_max=268435456 nvx_pids_max=65\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen=//./pipe/openvmm-microvm-{hex32}\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root_json}\\\\state\\\\outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"quiet\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"worker\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": 268435456,\r\n \"memory_mib\": 384,\r\n \"mount\": null,\r\n \"network\": {\r\n \"allow\": {\r\n \"name\": \"--network-egress-allow\",\r\n \"rules\": [\r\n {\r\n \"ports\": [\r\n {\r\n \"endPort\": null,\r\n \"port\": 443,\r\n \"protocol\": \"tcp\"\r\n }\r\n ],\r\n \"to\": [\r\n {\r\n \"cidr\": \"192.0.2.0/24\",\r\n \"except\": []\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n \"deny\": {\r\n \"name\": \"--network-egress-deny\",\r\n \"rules\": [\r\n {\r\n \"ports\": [],\r\n \"to\": [\r\n {\r\n \"cidr\": \"192.0.2.128/25\",\r\n \"except\": []\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n \"egress\": \"allow\",\r\n \"guestNetwork\": \"10.0.0.2/24\",\r\n \"hostLoopback\": \"allow\",\r\n \"hostLoopbackForwards\": [\r\n {\r\n \"guestPort\": 8080,\r\n \"hostPort\": 18080,\r\n \"protocol\": \"tcp\"\r\n },\r\n {\r\n \"guestPort\": 9090,\r\n \"hostPort\": 19090,\r\n \"protocol\": \"udp\"\r\n }\r\n ],\r\n \"ingress\": \"deny\",\r\n \"proxy\": null\r\n },\r\n \"pids_max\": 64,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 1000,\r\n \"workload_uid\": 1000\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 1,\n \"deny_rule_count\": 1,\n \"host_loopback\": \"allow\",\n \"mode\": \"rules\",\n \"status\": \"applied\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"quiet\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"worker\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": 268435456,\r\n \"memory_mib\": 384,\r\n \"mount\": null,\r\n \"network\": {\r\n \"allow\": {\r\n \"name\": \"--network-egress-allow\",\r\n \"rules\": [\r\n {\r\n \"ports\": [\r\n {\r\n \"endPort\": null,\r\n \"port\": 443,\r\n \"protocol\": \"tcp\"\r\n }\r\n ],\r\n \"to\": [\r\n {\r\n \"cidr\": \"192.0.2.0/24\",\r\n \"except\": []\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n \"deny\": {\r\n \"name\": \"--network-egress-deny\",\r\n \"rules\": [\r\n {\r\n \"ports\": [],\r\n \"to\": [\r\n {\r\n \"cidr\": \"192.0.2.128/25\",\r\n \"except\": []\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n \"egress\": \"allow\",\r\n \"guestNetwork\": \"10.0.0.2/24\",\r\n \"hostLoopback\": \"allow\",\r\n \"hostLoopbackForwards\": [\r\n {\r\n \"guestPort\": 8080,\r\n \"hostPort\": 18080,\r\n \"protocol\": \"tcp\"\r\n },\r\n {\r\n \"guestPort\": 9090,\r\n \"hostPort\": 19090,\r\n \"protocol\": \"udp\"\r\n }\r\n ],\r\n \"ingress\": \"deny\",\r\n \"proxy\": null\r\n },\r\n \"pids_max\": 64,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 1000,\r\n \"workload_uid\": 1000\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"quiet\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"worker\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": 268435456,\r\n \"memory_mib\": 384,\r\n \"mount\": null,\r\n \"network\": {\r\n \"allow\": {\r\n \"name\": \"--network-egress-allow\",\r\n \"rules\": [\r\n {\r\n \"ports\": [\r\n {\r\n \"endPort\": null,\r\n \"port\": 443,\r\n \"protocol\": \"tcp\"\r\n }\r\n ],\r\n \"to\": [\r\n {\r\n \"cidr\": \"192.0.2.0/24\",\r\n \"except\": []\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n \"deny\": {\r\n \"name\": \"--network-egress-deny\",\r\n \"rules\": [\r\n {\r\n \"ports\": [],\r\n \"to\": [\r\n {\r\n \"cidr\": \"192.0.2.128/25\",\r\n \"except\": []\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n \"egress\": \"allow\",\r\n \"guestNetwork\": \"10.0.0.2/24\",\r\n \"hostLoopback\": \"allow\",\r\n \"hostLoopbackForwards\": [\r\n {\r\n \"guestPort\": 8080,\r\n \"hostPort\": 18080,\r\n \"protocol\": \"tcp\"\r\n },\r\n {\r\n \"guestPort\": 9090,\r\n \"hostPort\": 19090,\r\n \"protocol\": \"udp\"\r\n }\r\n ],\r\n \"ingress\": \"deny\",\r\n \"proxy\": null\r\n },\r\n \"pids_max\": 64,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 1000,\r\n \"workload_uid\": 1000\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 1,\n \"deny_rule_count\": 1,\n \"host_loopback\": \"allow\",\n \"mode\": \"rules\",\n \"status\": \"applied\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, -{"name":"lifecycle: proxy-only network","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--net","10.0.0.2/24","--network-profile","portable","--network-egress","deny","--host-loopback","deny","--network-proxy","10.0.0.1:3128"],"check":["state/config.json"],"status":0,"stdout":"","stderr":"","files":{"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"network\": {\r\n \"allow\": {\r\n \"name\": \"--network-egress-allow\",\r\n \"rules\": []\r\n },\r\n \"deny\": {\r\n \"name\": \"--network-egress-deny\",\r\n \"rules\": []\r\n },\r\n \"egress\": \"deny\",\r\n \"guestNetwork\": \"10.0.0.2/24\",\r\n \"hostLoopback\": \"deny\",\r\n \"hostLoopbackForwards\": [],\r\n \"ingress\": \"deny\",\r\n \"proxy\": {\r\n \"port\": 3128,\r\n \"scheme\": null\r\n }\r\n },\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"}}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--net\",\"10.0.0.2/24\",\"--network-profile\",\"portable\",\"--network-egress\",\"deny\",\"--network-ingress\",\"deny\",\"--host-loopback\",\"deny\",\"--network-proxy\",\"10.0.0.1:3128\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"256M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen=//./pipe/openvmm-microvm-{hex32}\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root_json}\\\\state\\\\outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}]}, +{"name":"lifecycle: minimal","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:\\\\\\\\?\\\\{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:\\\\\\\\?\\\\{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"256M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen=//./pipe/openvmm-microvm-{hex32}\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root_json}\\\\state\\\\outcome.json\"]"}}},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/echo","--arg","hello","--arg","world"],"status":0,"stdout":"hello world\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/pwd","--cwd","/tmp"],"status":0,"stdout":"/tmp\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/sh","--arg=-c","--arg","echo out; echoerr err; exit 3","--outcome-report","{root}\\reports\\exec.json"],"check":["reports/exec.json"],"status":3,"stdout":"out\n","stderr":"err\n","files":{"reports/exec.json":{"text":"{\n \"operation_id\": \"{hex32}\",\n \"outcome\": {\n \"category\": \"exit\",\n \"operation\": \"exec\",\n \"status_code\": 3\n },\n \"schema_version\": 1\n}\n"}}},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/usr/bin/env","--environment","A=1","--environment","B=two"],"status":0,"stdout":"A=1\nB=two\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/usr/bin/printenv","--arg","PATH","--inherit-default-environment","--environment","A=1"],"status":0,"stdout":"/usr/sbin:/usr/bin:/sbin:/bin\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/usr/bin/env","--environment-file","{root}\\env.json"],"write":{"env.json":"[\"X=1\", \"Y=two words\"]"},"status":0,"stdout":"X=1\nY=two words\n","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/usr/bin/env","--environment-file","{root}\\empty.json"],"write":{"empty.json":"[]"},"status":0,"stdout":"","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/sh","--arg=-c","--arg","sleep 5000","--exec-timeout-ms","50","--outcome-report","{root}\\reports\\exec-timeout.json"],"check":["reports/exec-timeout.json"],"status":124,"stdout":"","stderr":"","files":{"reports/exec-timeout.json":{"text":"{\n \"operation_id\": \"{hex32}\",\n \"outcome\": {\n \"category\": \"timeout\",\n \"operation\": \"exec\",\n \"status_code\": 124\n },\n \"schema_version\": 1\n}\n"}}},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/sh","--arg=-c","--arg","signal 9"],"status":137,"stdout":"","stderr":""},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/missing"],"status":127,"stdout":"","stderr":"openvmm: /bin/missing: not found\n"},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/pwd","--cwd","/missing"],"status":1,"stdout":"","stderr":"error: managed guest rejected exec (status=2, category=cwd-failed)\r\n"},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/pwd","--cwd","relative"],"status":1,"stdout":"","stderr":"error: managed exec working directory must be an absolute path of at most 4096 bytes\r\n"},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/echo","--outcome-report","{root}\\reports\\exec.json"],"status":1,"stdout":"","stderr":"error: outcome report already exists: {root}\\reports\\exec.json\r\n"},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/echo","--outcome-report","{root}\\missing\\exec.json"],"status":1,"stdout":"","stderr":"error: outcome report parent is not a plain directory: {root}\\missing\r\n"},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: one share","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 2,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 2,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:\\\\\\\\?\\\\{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:\\\\\\\\?\\\\{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount\",\"/data,{root_json}\\\\share,ro\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"256M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen=//./pipe/openvmm-microvm-{hex32}\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root_json}\\\\state\\\\outcome.json\"]"}}},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/pwd","--cwd","/data"],"status":0,"stdout":"/data\n","stderr":""},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 2,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 2,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 2,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: several shares","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share","--mount","/other,{root}\\share2,rw"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 4,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n {\r\n \"access\": \"rw\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/other\",\r\n \"host_path\": \"{root_json}\\\\share2\",\r\n \"owner\": \"vmm\"\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 4,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n {\r\n \"access\": \"rw\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/other\",\r\n \"host_path\": \"{root_json}\\\\share2\",\r\n \"owner\": \"vmm\"\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:\\\\\\\\?\\\\{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:\\\\\\\\?\\\\{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount-aggregate\",\"/run/nvx/shares\",\"--mount-child\",\"0-bd47413b5c03dfc6e4b5b8143fe205ea,{root_json}\\\\share,ro\",\"--mount-child\",\"1-bf2faee2596026620b5cbc997c2d7d45,{root_json}\\\\share2,rw\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"256M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox nvx_share=0-bd47413b5c03dfc6e4b5b8143fe205ea,/data,ro nvx_share=1-bf2faee2596026620b5cbc997c2d7d45,/other,rw\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen=//./pipe/openvmm-microvm-{hex32}\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root_json}\\\\state\\\\outcome.json\"]"}}},{"argv":["sandbox","exec","--state-dir","{root}\\state","--entrypoint","/bin/pwd","--cwd","/other"],"status":0,"stdout":"/other\n","stderr":""},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 4,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n {\r\n \"access\": \"rw\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/other\",\r\n \"host_path\": \"{root_json}\\\\share2\",\r\n \"owner\": \"vmm\"\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 4,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n {\r\n \"access\": \"rw\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/other\",\r\n \"host_path\": \"{root_json}\\\\share2\",\r\n \"owner\": \"vmm\"\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 4,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\"\r\n },\r\n {\r\n \"access\": \"rw\",\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/other\",\r\n \"host_path\": \"{root_json}\\\\share2\",\r\n \"owner\": \"vmm\"\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: share policies","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/data,{root}\\share,rw","--mount-deny","{root}\\share\\hidden","--mount-allow","{root}\\share\\hidden\\shown","--mount-write","{root}\\share\\writable"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 5,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"rw\",\r\n \"allowed_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\\\\shown\"\r\n ],\r\n \"denied_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\"\r\n ],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": [\r\n \"{root_json}\\\\share\\\\writable\"\r\n ]\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 5,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"rw\",\r\n \"allowed_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\\\\shown\"\r\n ],\r\n \"denied_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\"\r\n ],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": [\r\n \"{root_json}\\\\share\\\\writable\"\r\n ]\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:\\\\\\\\?\\\\{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:\\\\\\\\?\\\\{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount\",\"/data,{root_json}\\\\share,rw\",\"--mount-deny\",\"{root_json}\\\\share\\\\hidden\",\"--mount-allow\",\"{root_json}\\\\share\\\\hidden\\\\shown\",\"--mount-write\",\"{root_json}\\\\share\\\\writable\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"256M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen=//./pipe/openvmm-microvm-{hex32}\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root_json}\\\\state\\\\outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 5,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"rw\",\r\n \"allowed_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\\\\shown\"\r\n ],\r\n \"denied_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\"\r\n ],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": [\r\n \"{root_json}\\\\share\\\\writable\"\r\n ]\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 5,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"rw\",\r\n \"allowed_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\\\\shown\"\r\n ],\r\n \"denied_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\"\r\n ],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": [\r\n \"{root_json}\\\\share\\\\writable\"\r\n ]\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 5,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"rw\",\r\n \"allowed_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\\\\shown\"\r\n ],\r\n \"denied_paths\": [\r\n \"{root_json}\\\\share\\\\hidden\"\r\n ],\r\n \"guest_target\": \"/data\",\r\n \"host_path\": \"{root_json}\\\\share\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": [\r\n \"{root_json}\\\\share\\\\writable\"\r\n ]\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: file share","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--mount","/etc/single.txt,{root}\\single.txt,ro"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 6,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"allowed_paths\": [],\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/etc/single.txt\",\r\n \"host_path\": \"{root_json}\\\\single.txt\",\r\n \"kind\": \"file\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": []\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 6,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"allowed_paths\": [],\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/etc/single.txt\",\r\n \"host_path\": \"{root_json}\\\\single.txt\",\r\n \"kind\": \"file\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": []\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:\\\\\\\\?\\\\{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:\\\\\\\\?\\\\{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--mount-aggregate\",\"/run/nvx/shares\",\"--mount-child\",\"0-a80a05e9cb129bc27b7ba103bf2f5ac0,{root_json}\\\\single.txt,ro,file\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"256M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox nvx_share=0-a80a05e9cb129bc27b7ba103bf2f5ac0,/etc/single.txt,ro,file\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen=//./pipe/openvmm-microvm-{hex32}\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root_json}\\\\state\\\\outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 6,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"allowed_paths\": [],\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/etc/single.txt\",\r\n \"host_path\": \"{root_json}\\\\single.txt\",\r\n \"kind\": \"file\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": []\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 6,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"allowed_paths\": [],\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/etc/single.txt\",\r\n \"host_path\": \"{root_json}\\\\single.txt\",\r\n \"kind\": \"file\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": []\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 6,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mounts\": [\r\n {\r\n \"access\": \"ro\",\r\n \"allowed_paths\": [],\r\n \"denied_paths\": [],\r\n \"guest_target\": \"/etc/single.txt\",\r\n \"host_path\": \"{root_json}\\\\single.txt\",\r\n \"kind\": \"file\",\r\n \"owner\": \"vmm\",\r\n \"writable_paths\": []\r\n }\r\n ],\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 0,\n \"deny_rule_count\": 0,\n \"host_loopback\": \"none\",\n \"mode\": \"none\",\n \"status\": \"not-requested\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: network and launch options","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--entrypoint","/bin/server","--arg=--serve","--hostname","worker","--workload-user","1000:1000","--memory-max","268435456","--pids-max","64","--memory-mib","384","--hypervisor","auto","--cmdline","quiet","--net","10.0.0.2/24","--network-profile","portable","--network-egress","allow","--network-ingress","deny","--network-egress-policy-file","{root}\\policy.json","--host-loopback","allow","--host-loopback-forward","tcp:18080:8080","--host-loopback-forward","udp:19090:9090"],"write":{"policy.json":"{\"allow\": [{\"cidr\": \"192.0.2.0/24\", \"protocol\": \"tcp\", \"port\": 443}], \"deny\": [{\"cidr\": \"192.0.2.128/25\"}]}"},"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"quiet\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"worker\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": 268435456,\r\n \"memory_mib\": 384,\r\n \"mount\": null,\r\n \"network\": {\r\n \"allow\": {\r\n \"name\": \"--network-egress-allow\",\r\n \"rules\": [\r\n {\r\n \"ports\": [\r\n {\r\n \"endPort\": null,\r\n \"port\": 443,\r\n \"protocol\": \"tcp\"\r\n }\r\n ],\r\n \"to\": [\r\n {\r\n \"cidr\": \"192.0.2.0/24\",\r\n \"except\": []\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n \"deny\": {\r\n \"name\": \"--network-egress-deny\",\r\n \"rules\": [\r\n {\r\n \"ports\": [],\r\n \"to\": [\r\n {\r\n \"cidr\": \"192.0.2.128/25\",\r\n \"except\": []\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n \"egress\": \"allow\",\r\n \"guestNetwork\": \"10.0.0.2/24\",\r\n \"hostLoopback\": \"allow\",\r\n \"hostLoopbackForwards\": [\r\n {\r\n \"guestPort\": 8080,\r\n \"hostPort\": 18080,\r\n \"protocol\": \"tcp\"\r\n },\r\n {\r\n \"guestPort\": 9090,\r\n \"hostPort\": 19090,\r\n \"protocol\": \"udp\"\r\n }\r\n ],\r\n \"ingress\": \"deny\",\r\n \"proxy\": null\r\n },\r\n \"pids_max\": 64,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 1000,\r\n \"workload_uid\": 1000\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log","build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"quiet\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"worker\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": 268435456,\r\n \"memory_mib\": 384,\r\n \"mount\": null,\r\n \"network\": {\r\n \"allow\": {\r\n \"name\": \"--network-egress-allow\",\r\n \"rules\": [\r\n {\r\n \"ports\": [\r\n {\r\n \"endPort\": null,\r\n \"port\": 443,\r\n \"protocol\": \"tcp\"\r\n }\r\n ],\r\n \"to\": [\r\n {\r\n \"cidr\": \"192.0.2.0/24\",\r\n \"except\": []\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n \"deny\": {\r\n \"name\": \"--network-egress-deny\",\r\n \"rules\": [\r\n {\r\n \"ports\": [],\r\n \"to\": [\r\n {\r\n \"cidr\": \"192.0.2.128/25\",\r\n \"except\": []\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n \"egress\": \"allow\",\r\n \"guestNetwork\": \"10.0.0.2/24\",\r\n \"hostLoopback\": \"allow\",\r\n \"hostLoopbackForwards\": [\r\n {\r\n \"guestPort\": 8080,\r\n \"hostPort\": 18080,\r\n \"protocol\": \"tcp\"\r\n },\r\n {\r\n \"guestPort\": 9090,\r\n \"hostPort\": 19090,\r\n \"protocol\": \"udp\"\r\n }\r\n ],\r\n \"ingress\": \"deny\",\r\n \"proxy\": null\r\n },\r\n \"pids_max\": 64,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 1000,\r\n \"workload_uid\": 1000\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""},"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:\\\\\\\\?\\\\{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:\\\\\\\\?\\\\{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"1000:1000\",\"--net\",\"10.0.0.2/24\",\"--network-profile\",\"portable\",\"--network-egress\",\"allow\",\"--network-ingress\",\"deny\",\"--host-loopback\",\"allow\",\"--network-egress-allow\",\"192.0.2.0/24:tcp:443\",\"--network-egress-deny\",\"192.0.2.128/25\",\"--host-loopback-forward\",\"tcp:18080:8080\",\"--host-loopback-forward\",\"udp:19090:9090\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"384M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"quiet nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=worker nvx_memory_max=268435456 nvx_pids_max=65\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen=//./pipe/openvmm-microvm-{hex32}\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root_json}\\\\state\\\\outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"quiet\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"worker\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": 268435456,\r\n \"memory_mib\": 384,\r\n \"mount\": null,\r\n \"network\": {\r\n \"allow\": {\r\n \"name\": \"--network-egress-allow\",\r\n \"rules\": [\r\n {\r\n \"ports\": [\r\n {\r\n \"endPort\": null,\r\n \"port\": 443,\r\n \"protocol\": \"tcp\"\r\n }\r\n ],\r\n \"to\": [\r\n {\r\n \"cidr\": \"192.0.2.0/24\",\r\n \"except\": []\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n \"deny\": {\r\n \"name\": \"--network-egress-deny\",\r\n \"rules\": [\r\n {\r\n \"ports\": [],\r\n \"to\": [\r\n {\r\n \"cidr\": \"192.0.2.128/25\",\r\n \"except\": []\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n \"egress\": \"allow\",\r\n \"guestNetwork\": \"10.0.0.2/24\",\r\n \"hostLoopback\": \"allow\",\r\n \"hostLoopbackForwards\": [\r\n {\r\n \"guestPort\": 8080,\r\n \"hostPort\": 18080,\r\n \"protocol\": \"tcp\"\r\n },\r\n {\r\n \"guestPort\": 9090,\r\n \"hostPort\": 19090,\r\n \"protocol\": \"udp\"\r\n }\r\n ],\r\n \"ingress\": \"deny\",\r\n \"proxy\": null\r\n },\r\n \"pids_max\": 64,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 1000,\r\n \"workload_uid\": 1000\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 1,\n \"deny_rule_count\": 1,\n \"host_loopback\": \"allow\",\n \"mode\": \"rules\",\n \"status\": \"applied\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","control.capability","openvmm.log","runtime.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"quiet\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"worker\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": 268435456,\r\n \"memory_mib\": 384,\r\n \"mount\": null,\r\n \"network\": {\r\n \"allow\": {\r\n \"name\": \"--network-egress-allow\",\r\n \"rules\": [\r\n {\r\n \"ports\": [\r\n {\r\n \"endPort\": null,\r\n \"port\": 443,\r\n \"protocol\": \"tcp\"\r\n }\r\n ],\r\n \"to\": [\r\n {\r\n \"cidr\": \"192.0.2.0/24\",\r\n \"except\": []\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n \"deny\": {\r\n \"name\": \"--network-egress-deny\",\r\n \"rules\": [\r\n {\r\n \"ports\": [],\r\n \"to\": [\r\n {\r\n \"cidr\": \"192.0.2.128/25\",\r\n \"except\": []\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n \"egress\": \"allow\",\r\n \"guestNetwork\": \"10.0.0.2/24\",\r\n \"hostLoopback\": \"allow\",\r\n \"hostLoopbackForwards\": [\r\n {\r\n \"guestPort\": 8080,\r\n \"hostPort\": 18080,\r\n \"protocol\": \"tcp\"\r\n },\r\n {\r\n \"guestPort\": 9090,\r\n \"hostPort\": 19090,\r\n \"protocol\": \"udp\"\r\n }\r\n ],\r\n \"ingress\": \"deny\",\r\n \"proxy\": null\r\n },\r\n \"pids_max\": 64,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 1000,\r\n \"workload_uid\": 1000\r\n}\r\n"},"state/runtime.json":{"text":"{\n \"format\": 4,\n \"launch\": \"{hex32}\",\n \"process\": {\n \"pid\": {int},\n \"startTime\": {int},\n \"boot\": \"{hex32}\"\n },\n \"endpoint\": \"//./pipe/openvmm-microvm-{hex32}\",\n \"runtime\": {\n \"openvmm\": \"sha256:1e8c924cdaac227bf74b4a4a9bcdb9e8836a1355fd0408cc81dab5d9aa71eb4d\",\n \"kernel\": \"sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72\",\n \"initramfs\": \"sha256:2f77c2c97621e8bf69794e9d12815bb7c46338638f126bea8b18dfe02a684d62\",\n \"bootContract\": 2,\n \"controlContract\": \"nvx-microvm-v2-control-v2\",\n \"controlSession\": 1\n }\n}\n"},"state/control.capability":{"size":32},"state/outcome.json":null,"state/openvmm.log":{"text":""}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":0,"stdout":"","stderr":"","files":{"state":{"entries":["config.json","openvmm.log","outcome.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"quiet\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"worker\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": 268435456,\r\n \"memory_mib\": 384,\r\n \"mount\": null,\r\n \"network\": {\r\n \"allow\": {\r\n \"name\": \"--network-egress-allow\",\r\n \"rules\": [\r\n {\r\n \"ports\": [\r\n {\r\n \"endPort\": null,\r\n \"port\": 443,\r\n \"protocol\": \"tcp\"\r\n }\r\n ],\r\n \"to\": [\r\n {\r\n \"cidr\": \"192.0.2.0/24\",\r\n \"except\": []\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n \"deny\": {\r\n \"name\": \"--network-egress-deny\",\r\n \"rules\": [\r\n {\r\n \"ports\": [],\r\n \"to\": [\r\n {\r\n \"cidr\": \"192.0.2.128/25\",\r\n \"except\": []\r\n }\r\n ]\r\n }\r\n ]\r\n },\r\n \"egress\": \"allow\",\r\n \"guestNetwork\": \"10.0.0.2/24\",\r\n \"hostLoopback\": \"allow\",\r\n \"hostLoopbackForwards\": [\r\n {\r\n \"guestPort\": 8080,\r\n \"hostPort\": 18080,\r\n \"protocol\": \"tcp\"\r\n },\r\n {\r\n \"guestPort\": 9090,\r\n \"hostPort\": 19090,\r\n \"protocol\": \"udp\"\r\n }\r\n ],\r\n \"ingress\": \"deny\",\r\n \"proxy\": null\r\n },\r\n \"pids_max\": 64,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 1000,\r\n \"workload_uid\": 1000\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":{"text":"{\n \"backend\": \"whp\",\n \"instance_id\": \"{hex32}\",\n \"network_policy\": {\n \"allow_rule_count\": 1,\n \"deny_rule_count\": 1,\n \"host_loopback\": \"allow\",\n \"mode\": \"rules\",\n \"status\": \"applied\",\n \"status_code\": 0\n },\n \"outcome\": {\n \"category\": \"success\",\n \"operation\": \"managed\",\n \"status_code\": 0\n },\n \"schema_version\": 1,\n \"teardown\": {\n \"control_channels_closed\": true,\n \"guest_workload_stopped\": true,\n \"network_released\": true,\n \"openvmm_process_terminated\": true,\n \"temporary_storage_removed\": true,\n \"virtiofs_released\": true,\n \"vm_stopped\": true\n }\n}\n"},"state/openvmm.log":{"text":""}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"lifecycle: proxy-only network","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--net","10.0.0.2/24","--network-profile","portable","--network-egress","deny","--host-loopback","deny","--network-proxy","10.0.0.1:3128"],"check":["state/config.json"],"status":0,"stdout":"","stderr":"","files":{"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"network\": {\r\n \"allow\": {\r\n \"name\": \"--network-egress-allow\",\r\n \"rules\": []\r\n },\r\n \"deny\": {\r\n \"name\": \"--network-egress-deny\",\r\n \"rules\": []\r\n },\r\n \"egress\": \"deny\",\r\n \"guestNetwork\": \"10.0.0.2/24\",\r\n \"hostLoopback\": \"deny\",\r\n \"hostLoopbackForwards\": [],\r\n \"ingress\": \"deny\",\r\n \"proxy\": {\r\n \"port\": 3128,\r\n \"scheme\": null\r\n }\r\n },\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"}}},{"argv":["sandbox","start","--state-dir","{root}\\state"],"check":["build/fake-openvmm-state.json"],"status":0,"stdout":"","stderr":"","files":{"build/fake-openvmm-state.json":{"text":"[\"--machine\",\"microvm\",\"--microvm-sandbox-block\",\"distro:file:\\\\\\\\?\\\\{root_json}\\\\distro.erofs,ro\",\"--microvm-sandbox-block\",\"scratch:file:\\\\\\\\?\\\\{root_json}\\\\scratch.ext4\",\"--microvm-workload-identity\",\"65534:65534\",\"--net\",\"10.0.0.2/24\",\"--network-profile\",\"portable\",\"--network-egress\",\"deny\",\"--network-ingress\",\"deny\",\"--host-loopback\",\"deny\",\"--network-proxy\",\"10.0.0.1:3128\",\"--microvm-lifecycle\",\"managed\",\"--single-process\",\"--hypervisor\",\"whp\",\"--memory\",\"256M\",\"--kernel\",\"{root_json}\\\\build\\\\vmlinux\",\"--initrd\",\"{root_json}\\\\build\\\\initramfs.cpio.gz\",\"--cmdline\",\"nvx_sandbox=1 nvx_layer=distro,0xd0003000,erofs:11111111-1111-1111-1111-111111111111 nvx_scratch=0xd0006000,ext4 nvx_entrypoint=/bin/sh nvx_hostname=nvx-sandbox\",\"--virtio-console\",\"none\",\"--microvm-control-console\",\"listen=//./pipe/openvmm-microvm-{hex32}\",\"--microvm-control-auth-stdin\",\"--microvm-report\",\"{root_json}\\\\state\\\\outcome.json\"]"}}},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}]}, {"name":"state: operations without a sandbox","steps":[{"argv":["sandbox","start","--state-dir","{root}\\state"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}\\state\r\n"},{"argv":["sandbox","exec","--state-dir","{root}\\state"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}\\state\r\n"},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}\\state\r\n"},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}\\state\r\n"}]}, {"name":"state: refusals of a running sandbox","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"status":1,"stdout":"","stderr":"error: sandbox is already provisioned\r\n"},{"argv":["sandbox","start","--state-dir","{root}\\state"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","start","--state-dir","{root}\\state"],"status":1,"stdout":"","stderr":"error: sandbox is already running or has stale runtime state\r\n"},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"status":1,"stdout":"","stderr":"error: sandbox must be stopped before deprovision\r\n"},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","stop","--state-dir","{root}\\state"],"status":1,"stdout":"","stderr":"error: sandbox is not running\r\n"},{"argv":["sandbox","exec","--state-dir","{root}\\state"],"status":1,"stdout":"","stderr":"error: sandbox is not running\r\n"},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, -{"name":"state: relative and nested state directory","steps":[{"argv":["sandbox","provision","--state-dir","nested/state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"check":["nested/state","nested/state/config.json"],"status":0,"stdout":"","stderr":"","files":{"nested/state":{"entries":["config.json","sandbox.lock"]},"nested/state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"}}},{"argv":["sandbox","deprovision","--state-dir","nested/state"],"check":["nested/state"],"status":0,"stdout":"","stderr":"","files":{"nested/state":null}}]}, -{"name":"state: failed start","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--cmdline","fake_exit_on_start=5"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","start","--state-dir","{root}\\state","--timeout","0.5"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":1,"stdout":"","stderr":"error: OpenVMM exited during startup with status 5\r\n","files":{"state":{"entries":["config.json","openvmm.log","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"fake_exit_on_start=5\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":{"text":"openvmm: failing the launch as requested\n"}}},{"argv":["sandbox","start","--state-dir","{root}\\state","--timeout","0.5"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":1,"stdout":"","stderr":"error: OpenVMM exited during startup with status 5\r\n","files":{"state":{"entries":["config.json","openvmm.log","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"fake_exit_on_start=5\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":{"text":"openvmm: failing the launch as requested\n"}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, -{"name":"state: start without OpenVMM","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","start","--state-dir","{root}\\state"],"remove":["openvmm/target/release/openvmm{exe}"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":1,"stdout":"","stderr":"error: OpenVMM release binary not found: {root}\\openvmm\\target\\release\\openvmm.exe\r\n","files":{"state":{"entries":["config.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"status":0,"stdout":"","stderr":""}]}, +{"name":"state: relative and nested state directory","steps":[{"argv":["sandbox","provision","--state-dir","nested/state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"check":["nested/state","nested/state/config.json"],"status":0,"stdout":"","stderr":"","files":{"nested/state":{"entries":["config.json","sandbox.lock"]},"nested/state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"}}},{"argv":["sandbox","deprovision","--state-dir","nested/state"],"check":["nested/state"],"status":0,"stdout":"","stderr":"","files":{"nested/state":null}}]}, +{"name":"state: failed start","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4","--cmdline","fake_exit_on_start=5"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","start","--state-dir","{root}\\state","--timeout","0.5"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":1,"stdout":"","stderr":"error: OpenVMM exited during startup with status 5\r\n","files":{"state":{"entries":["config.json","openvmm.log","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"fake_exit_on_start=5\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":{"text":"openvmm: failing the launch as requested\n"}}},{"argv":["sandbox","start","--state-dir","{root}\\state","--timeout","0.5"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":1,"stdout":"","stderr":"error: OpenVMM exited during startup with status 5\r\n","files":{"state":{"entries":["config.json","openvmm.log","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"fake_exit_on_start=5\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":{"text":"openvmm: failing the launch as requested\n"}}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}],"interop":true}, +{"name":"state: start without OpenVMM","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","start","--state-dir","{root}\\state"],"remove":["openvmm/target/release/openvmm{exe}"],"check":["state","state/config.json","state/runtime.json","state/control.capability","state/outcome.json","state/openvmm.log"],"status":1,"stdout":"","stderr":"error: OpenVMM release binary not found: {root}\\openvmm\\target\\release\\openvmm.exe\r\n","files":{"state":{"entries":["config.json","sandbox.lock"]},"state/config.json":{"text":"{\r\n \"cmdline\": \"\",\r\n \"distro_binding\": {\r\n \"format\": \"erofs\",\r\n \"identity\": \"11111111-1111-1111-1111-111111111111\",\r\n \"image\": {\r\n \"changed\": null,\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 7,\r\n \"modified\": {\r\n \"nanoseconds\": {int},\r\n \"seconds\": {int}\r\n },\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"manifest\": null,\r\n \"policy\": {\r\n \"result\": \"accepted\",\r\n \"revision\": 1\r\n },\r\n \"version\": 1\r\n },\r\n \"format\": 1,\r\n \"hostname\": \"nvx-sandbox\",\r\n \"hypervisor\": \"whp\",\r\n \"layers\": [\r\n {\r\n \"path\": \"{root_json}\\\\distro.erofs\",\r\n \"role\": \"distro\",\r\n \"uuid\": \"11111111-1111-1111-1111-111111111111\"\r\n }\r\n ],\r\n \"memory_max\": null,\r\n \"memory_mib\": 256,\r\n \"mount\": null,\r\n \"network\": null,\r\n \"pids_max\": null,\r\n \"scratch\": \"{root_json}\\\\scratch.ext4\",\r\n \"scratch_binding\": {\r\n \"device\": {int},\r\n \"fileId\": \"{hex32}\",\r\n \"kind\": \"regular\",\r\n \"length\": 8,\r\n \"scheme\": \"windows\",\r\n \"version\": 1\r\n },\r\n \"workload_gid\": 65534,\r\n \"workload_uid\": 65534\r\n}\r\n"},"state/runtime.json":null,"state/control.capability":null,"state/outcome.json":null,"state/openvmm.log":null}},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"status":0,"stdout":"","stderr":""}]}, {"name":"state: malformed configuration","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","start","--state-dir","{root}\\state"],"write":{"state/config.json":"{\"format\": 7}"},"status":1,"stdout":"","stderr":"error: sandbox configuration has an unsupported format: {root}\\state\\config.json\r\n"},{"argv":["sandbox","start","--state-dir","{root}\\state"],"write":{"state/config.json":"[]"},"status":1,"stdout":"","stderr":"error: sandbox configuration has an unsupported format: {root}\\state\\config.json\r\n"},{"argv":["sandbox","start","--state-dir","{root}\\state"],"write":{"state/config.json":"{\"format\": 1}"},"status":1,"stdout":"","stderr":"error: sandbox configuration is malformed\r\n"},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"check":["state"],"status":0,"stdout":"","stderr":"","files":{"state":null}}]}, {"name":"state: not a directory","steps":[{"argv":["sandbox","start","--state-dir","{root}\\single.txt"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}\\single.txt\r\n"},{"argv":["sandbox","provision","--state-dir","{root}\\single.txt","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"status":1,"stdout":"","stderr":"error: [WinError 183] Cannot create a file when that file already exists: '{root_json}\\\\single.txt'\r\n"},{"argv":["sandbox","deprovision","--state-dir","{root}\\single.txt"],"status":1,"stdout":"","stderr":"error: sandbox state path is not a plain directory: {root}\\single.txt\r\n"}]}, {"name":"state: foreign file","steps":[{"argv":["sandbox","provision","--state-dir","{root}\\state","--layer","distro,{root}\\distro.erofs,11111111-1111-1111-1111-111111111111","--scratch","{root}\\scratch.ext4"],"status":0,"stdout":"","stderr":""},{"argv":["sandbox","deprovision","--state-dir","{root}\\state"],"write":{"state/foreign.txt":"x"},"check":["state"],"status":1,"stdout":"","stderr":"error: sandbox state directory contains files not owned by NVX: foreign.txt\r\n","files":{"state":{"entries":["foreign.txt"]}}}]}