Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b93206a
host: give each VM a private directory with fixed names
Nov 5, 2026
a5ebdde
host: give a VM's machine, root, view and console one checked value
Nov 4, 2026
3bd7e0d
test-microvm: bound polling over static loopback fixtures
Nov 6, 2026
74b048c
nvx-rs: carry the root's settings in its boot tokens, and cap a RAM u…
Nov 4, 2026
8425d43
test-adversarial: await the fixture's surviving child before cleanup
Nov 6, 2026
ebbb956
test-adversarial: ready the contained fixture before timing its work
Nov 6, 2026
a9add81
test-adversarial: format the contained fixture's readiness helper
Nov 6, 2026
d9cf939
host: make the network and the cold boot part of a VM's configuration
Nov 5, 2026
2f6feb7
guest: validate a bound before refusing its GPT image
Nov 7, 2026
72d2d4d
nvx: combine configuration and root-token inputs
Nov 7, 2026
d45f17e
nvx: retain the published configuration squash ancestry
Nov 7, 2026
e5fc81f
nvx-rs: carry the root's settings in its boot tokens, and cap a RAM u…
Nov 4, 2026
670c3cf
test-adversarial: await the fixture's surviving child before cleanup
Nov 6, 2026
3409463
test-adversarial: ready the contained fixture before timing its work
Nov 6, 2026
527b32e
test-adversarial: format the contained fixture's readiness helper
Nov 6, 2026
5b7f85d
guest: validate a bound before refusing its GPT image
Nov 7, 2026
d45a9b5
nvx: retain the current root-token producer ancestry
Nov 7, 2026
9fa5f05
host: render cold-boot kernel tokens from the VM configuration
Nov 6, 2026
68373ce
doc: describe cold-boot view tokens and configuration rendering
Nov 6, 2026
4068b5b
host: refuse caller overrides of rendered boot tokens
Nov 6, 2026
a967f69
doc: describe caller boot tokens and mapping queries
Nov 6, 2026
f3479a0
host: render cold boots from runtime and VM configuration
Nov 7, 2026
a7f9eaa
host: document cold-boot argument validation
Nov 7, 2026
ae0cb82
host: compose pinned launch configuration inputs
Nov 7, 2026
56b0d78
host: compose pinned VM directory inputs
Nov 7, 2026
dfe09bf
doc: retain both producer module descriptions
Nov 7, 2026
506ce49
host: reject unrepresentable cold-boot disk paths
Nov 7, 2026
ddfe74e
host: prepare checked cold VM launches
Nov 7, 2026
f0b1480
host: compose corrected pinned configuration inputs
Nov 7, 2026
a5b3079
host: compose corrected configuration and VM directory inputs
Nov 7, 2026
9599516
host: consume corrected configuration path checks
Nov 7, 2026
1819dda
host: refuse unsupported control-console kernel syntax
Nov 7, 2026
b6fff99
host: compose corrected cold-boot input checks
Nov 7, 2026
8707d5a
host: consume corrected cold-boot token checks
Nov 7, 2026
28e2e95
host: hand prepared cold launches to external callers
Nov 7, 2026
2a4a737
host: record the owned command API and compile its external example
Nov 7, 2026
d1d803a
host: refuse unrepresentable native argument names
Nov 7, 2026
834099d
host: compose corrected native argument inputs
Nov 7, 2026
dc3d74c
host: consume corrected native argument representation
Nov 7, 2026
7c4156a
host: refuse unrepresentable cold VM socket paths
Nov 7, 2026
5c89457
host: carry corrected cold-launch preparation inputs
Nov 7, 2026
ae25aa4
host: preserve external launch paths and exit status
Nov 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions doc/design/4.1-guest-filesystem-and-image-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ stored and cached image bytes, because the guest kernel decompresses blocks as
it reads them. No FUSE daemon or in-guest fetch service is involved in the root
filesystem.

The launcher describes the root to the guest in two boot tokens, whose grammar
one contract module defines, with the host's renderer and a parser for guests:
The launcher describes the root to the guest in two boot tokens, and in up to
four more for its settings, whose grammar one contract module defines, with the
host's renderer and a parser for guests:

- `nvx_layer=distro,0xd0003000,FORMAT[:IDENTITY]` names the image on the
`distro` block device: its format, `erofs` for an EROFS filesystem or `gpt`
Expand All @@ -56,13 +57,23 @@ one contract module defines, with the host's renderer and a parser for guests:
- `nvx_scratch=0xd0006000,ext4` keeps the overlay's upper on the whole-device
ext4 `scratch` disk, and `nvx_overlay_upper=ramfs` keeps it in guest
memory, on a tmpfs, with no `scratch` block device.
- `nvx_overlay_mode=trusted-lowers` or `untrusted-lowers` names the overlay's
option set, `nvx_scratch_journal=allowed` or `forbidden` whether the
scratch's ext4 filesystem may have a journal, `nvx_first_partition_bound`
the most bytes of a GPT disk's first partition, and `nvx_ram_upper_percent`
the tmpfs upper's cap, from 1 to 90 percent of the guest's memory. A number
is decimal, without a sign or leading zeros. The guest takes its default for
a setting without a token: trusted lowers, a journal allowed, and the
kernel's default size of a tmpfs.

Guests refuse a token without a value, a repeated token, both uppers, an image
without an upper or an upper without an image, a RAM upper beside a `scratch`
block device, and every other form, including the earlier `nvx_layer` form
without a format, rather than probe the devices. The guest agents described
here assemble only an EROFS image, over either upper: they refuse a GPT image
with a message that says so.
without an upper or an upper or a setting without an image, a setting of
another upper or of another format, a RAM upper beside a `scratch` block
device, and every other form, including the earlier `nvx_layer` form without a
format, rather than probe the devices. The guest agents described here
assemble only an EROFS image, over either upper, for trusted lowers, and don't
check a scratch for a journal: they refuse a GPT image, untrusted lowers, and
a forbidden journal with a message that says so.

The guest agents assemble the root in this order:

Expand All @@ -77,8 +88,9 @@ The guest agents assemble the root in this order:
the token names one.
4. Mount the upper's file system, and create the overlay's upper and work
directories on it: resolve and flush scratch and mount it as ext4 with
`rw,nosuid,nodev`, or mount a tmpfs with `mode=0755,nosuid,nodev` at the
kernel's default size, half of the guest's RAM.
`rw,nosuid,nodev`, or mount a tmpfs with `mode=0755,nosuid,nodev`, and
`size=N%` when `nvx_ram_upper_percent=N` caps it, or else at the kernel's
default size, half of the guest's RAM.
5. Mount the overlay with the `distro` layer as its lower directory,
metadata-only copy-up, and extended inode numbers.
6. Verify the workload identity in the assembled root, mount each live share
Expand Down
94 changes: 75 additions & 19 deletions guest/common/nvx-init-agent
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,11 @@ is_canonical_uuid() {
return 1
}

# Reads the value of the nvx_layer token for read_root_tokens:
# distro,0xd0003000,FORMAT[:IDENTITY], where this init accepts only the erofs
# format, with or without the filesystem UUID that the image must have.
# Reads the nvx_layer format and EROFS identity for read_root_tokens.
# This init assembles only EROFS; it refuses GPT after root-token validation.
read_layer_token() {
layer_image=${1#distro,0xd0003000,}
layer_format=${layer_image%%:*}
case "$1" in
distro,0xd0003000,*,*) ;;
distro,0xd0003000,erofs)
Expand All @@ -196,10 +196,9 @@ read_layer_token() {
return 0
;;
distro,0xd0003000,gpt | distro,0xd0003000,gpt:*)
fatal "nvx_layer=$1 names a GPT image, which this init cannot assemble: it assembles only an EROFS image"
return 0
;;
distro,0xd0003000,*)
layer_format=${layer_image%%:*}
# The form without a format names the UUID in its place.
if [ -z "$layer_format" ] || is_canonical_uuid "$layer_format"; then
fatal "the nvx_layer kernel parameter names no image format, erofs or gpt: '$1'"
Expand All @@ -212,24 +211,36 @@ read_layer_token() {

# Reads the boot tokens of the layered root, with the rules of the host's
# renderer, nvx::protocol::root: nvx_layer names the image of the distro block
# device, and nvx_scratch=0xd0006000,ext4 or nvx_overlay_upper=ramfs the upper,
# each once. This init assembles only an EROFS image, so it also refuses a GPT
# image. Sets layer_uuid to the filesystem UUID that the image must have, or to
# nothing if the token names none, and upper to scratch or ram. The parser also
# refuses a RAM upper beside a scratch block device, which this init checks
# against the kernel's devices instead, with refuse_scratch_device. The tests
# replay nvx-rs/tests/fixtures/root-tokens.json through it.
# device, nvx_scratch=0xd0006000,ext4 or nvx_overlay_upper=ramfs the upper, and
# nvx_overlay_mode, nvx_scratch_journal, nvx_first_partition_bound, and
# nvx_ram_upper_percent the root's settings, each once. This init assembles
# only an EROFS image, mounts the overlay only for trusted lowers, and reads
# nothing of a scratch's superblock, so after those rules it refuses a GPT
# image, untrusted lowers, and a forbidden journal too. A bound is checked as
# an unsigned decimal even though it needs a GPT image, which this init cannot
# assemble. Sets layer_uuid to the filesystem UUID that the image must have, or to
# nothing if the token names none, upper to scratch or ram, and ram_percent to
# the RAM upper's cap, or to nothing for the kernel's default size. The parser
# also refuses a RAM upper beside a scratch block device, which this init
# checks against the kernel's devices instead, with refuse_scratch_device. The
# tests replay nvx-rs/tests/fixtures/root-tokens.json through it.
read_root_tokens() {
layer_seen=false
scratch_seen=false
ram_seen=false
layer_uuid=
# The names of the settings that the command line names, and the values of
# three of them.
settings=
overlay_mode=
scratch_journal=
ram_percent=
# Kernel command-line tokens are deliberately split on spaces.
# shellcheck disable=SC2086
set -f
for token in $cmdline; do
case "$token" in
nvx_layer | nvx_scratch | nvx_overlay_upper)
nvx_layer | nvx_scratch | nvx_overlay_upper | nvx_overlay_mode | nvx_scratch_journal | nvx_first_partition_bound | nvx_ram_upper_percent)
fatal "the $token kernel parameter has no value"
;;
nvx_layer=*)
Expand All @@ -252,6 +263,38 @@ read_root_tokens() {
[ "${token#*=}" = ramfs ] ||
fatal "the nvx_overlay_upper kernel parameter must be ramfs, not '${token#*=}'"
;;
nvx_overlay_mode=* | nvx_scratch_journal=* | nvx_first_partition_bound=* | nvx_ram_upper_percent=*)
setting=${token%%=*}
case " $settings " in *" $setting "*) fatal "the $setting kernel parameter is repeated" ;; esac
settings="$settings $setting"
case "$token" in
nvx_overlay_mode=trusted-lowers | nvx_overlay_mode=untrusted-lowers) overlay_mode=${token#*=} ;;
nvx_overlay_mode=*) fatal "the $setting kernel parameter must be trusted-lowers or untrusted-lowers, not '${token#*=}'" ;;
nvx_scratch_journal=allowed | nvx_scratch_journal=forbidden) scratch_journal=${token#*=} ;;
nvx_scratch_journal=*) fatal "the $setting kernel parameter must be allowed or forbidden, not '${token#*=}'" ;;
nvx_ram_upper_percent=[1-9] | nvx_ram_upper_percent=[1-8][0-9] | nvx_ram_upper_percent=90) ram_percent=${token#*=} ;;
nvx_ram_upper_percent=*) fatal "the $setting kernel parameter must be a decimal number from 1 to 90, without a sign or leading zeros, not '${token#*=}'" ;;
nvx_first_partition_bound= | nvx_first_partition_bound=0* | nvx_first_partition_bound=*[!0-9]*) fatal "the $setting kernel parameter must be a decimal number from 1 to 18446744073709551615, without a sign or leading zeros, not '${token#*=}'" ;;
nvx_first_partition_bound=*)
bound_digits=${token#*=}
[ "${#bound_digits}" -le 20 ] ||
fatal "the $setting kernel parameter must be a decimal number from 1 to 18446744073709551615, without a sign or leading zeros, not '${token#*=}'"
if [ "${#bound_digits}" -eq 20 ]; then
bound_limit=18446744073709551615
# Compare digits, never the full unsigned value.
while [ -n "$bound_digits" ]; do
bound_digit=${bound_digits%"${bound_digits#?}"}
limit_digit=${bound_limit%"${bound_limit#?}"}
[ "$bound_digit" -le "$limit_digit" ] ||
fatal "the $setting kernel parameter must be a decimal number from 1 to 18446744073709551615, without a sign or leading zeros, not '${token#*=}'"
[ "$bound_digit" -eq "$limit_digit" ] || break
bound_digits=${bound_digits#?}
bound_limit=${bound_limit#?}
done
fi
;;
esac
;;
esac
done
set +f
Expand All @@ -266,6 +309,19 @@ read_root_tokens() {
else
fatal "the nvx_layer kernel parameter needs an upper, nvx_scratch or nvx_overlay_upper"
fi
[ -z "$scratch_journal" ] || [ "$upper" = scratch ] ||
fatal "the nvx_scratch_journal kernel parameter needs a scratch upper, nvx_scratch"
[ -z "$ram_percent" ] || [ "$upper" = ram ] ||
fatal "the nvx_ram_upper_percent kernel parameter needs a RAM upper, nvx_overlay_upper"
if [ "$layer_format" != gpt ]; then
case " $settings " in *" nvx_first_partition_bound "*) fatal "the nvx_first_partition_bound kernel parameter needs a GPT image" ;; esac
fi
[ "$layer_format" != gpt ] ||
fatal "nvx_layer=distro,0xd0003000,$layer_image names a GPT image, which this init cannot assemble: it assembles only an EROFS image"
[ "$overlay_mode" != untrusted-lowers ] ||
fatal "nvx_overlay_mode=untrusted-lowers names untrusted lowers, which this init cannot assemble: it mounts the overlay only for trusted lowers, with metacopy=on,xino=on"
[ "$scratch_journal" != forbidden ] ||
fatal "nvx_scratch_journal=forbidden forbids the scratch a journal, which this init cannot check: it reads nothing of the scratch's superblock"
}

# Fails if the kernel has a virtio-mmio device at the scratch block device's
Expand All @@ -288,11 +344,11 @@ refuse_scratch_device() {
# Mounts the upper that read_root_tokens read, and creates the overlay's upper
# and work directories on it: the ext4 scratch disk at 0xd0006000, or a tmpfs,
# with the mode of an ext4 scratch's root rather than tmpfs's world-writable
# default, and with the kernel's default size, half of the guest's RAM, as
# nvx::guest::root mounts them. Sets upper_point to the upper's mount point,
# and upper_name to what messages call it. A tmpfs upper's pages are charged to
# the memory cgroup of the process that writes them, so the workload's writes
# count against its nvx_memory_max.
# default, and with the size of its cap, size=N%, or else the kernel's default
# size, half of the guest's RAM, as nvx::guest::root mounts them. Sets
# upper_point to the upper's mount point, and upper_name to what messages call
# it. A tmpfs upper's pages are charged to the memory cgroup of the process
# that writes them, so the workload's writes count against its nvx_memory_max.
mount_upper() {
case "$upper" in
scratch)
Expand All @@ -310,7 +366,7 @@ mount_upper() {
upper_point=$tmpfs
upper_name="the tmpfs upper"
mkdir -p "$upper_point"
mount -t tmpfs -o mode=0755,nosuid,nodev tmpfs "$upper_point" ||
mount -t tmpfs -o "mode=0755,nosuid,nodev${ram_percent:+,size=$ram_percent%}" tmpfs "$upper_point" ||
fatal "failed to mount the tmpfs upper"
;;
esac
Expand Down
5 changes: 5 additions & 0 deletions nvx-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ path = "src/bin/nvx-rs.rs"
required-features = ["cli"]
doc = false

[[example]]
name = "external-launch"
path = "examples/external-launch.rs"
required-features = ["host"]

# The guest binaries. Their unit tests live in the library, so the binaries have no test harness.
[[bin]]
name = "nvx-console-pending"
Expand Down
Loading
Loading