Repository navigation
SL2.4a1: the root's kernel tokens carry what VmConfig sets, and the guest caps a RAM upper - #1278
Conversation
This pull request changes existing testsIt modifies or removes 43 existing test asset(s), listed below. Review each with care: a change to a test can hide a regression that the test caught. See doc/contribute.md#changes-to-existing-tests. Test code
Test data
Relocated unchanged: 0; added: 7. Compared the merge base |
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues remain for the layered-root configuration and RAM-cap changes.
No open issues 🎉
What changed in this PR
Implements SL2.4a1 of #1098: extends NVX’s layered-root boot protocol to carry configuration settings and lets guests cap RAM-backed uppers. Complete VmConfig command-line rendering and activation-record changes remain deferred.
Changes:
- Adds optional overlay mode, journal policy, GPT partition bound, and RAM-cap tokens with strict parsing.
- Applies RAM caps through tmpfs
size=N%, while preserving existing defaults and contract versions. - Expands shared fixtures and tests, updates callers, and documents the new API and guest limitations.
| File | Description |
|---|---|
tests/runtime-adapter/tests/layered_launch.rs |
Keeps planned root settings absent. |
tests/runtime-adapter/tests/guest_session.rs |
Exercises forwarding a parsed RAM cap. |
tests/runtime-adapter/src/worker/activation/tests.rs |
Updates uncapped tmpfs construction. |
tests/runtime-adapter/src/worker.rs |
Passes the parsed cap to root assembly. |
tests/runtime-adapter/README.md |
Explains boot-token and adapter configuration responsibilities. |
scripts/test_nvx_tools.py |
Tests shell parsing, refusals, and capped mounts. |
nvx-rs/tests/fixtures/root-tokens.json |
Adds shared setting and validation cases. |
nvx-rs/src/protocol/root/tests.rs |
Expands round-trip, fixture, and contract coverage. |
nvx-rs/src/protocol/root.rs |
Adds optional settings, token rendering, and parsing. |
nvx-rs/src/host/template/plan/tests.rs |
Checks rejection of tenant-supplied root settings. |
nvx-rs/src/host/sandbox/tests.rs |
Preserves existing launch-token expectations. |
nvx-rs/src/host/sandbox/plan/tests.rs |
Tests rejection of settings absent from adapter plans. |
nvx-rs/src/host/sandbox/plan.rs |
Leaves new settings unset in existing plans. |
nvx-rs/src/guest/root/tests.rs |
Tests cap limits, mount options, and unchanged teardown. |
nvx-rs/src/guest/root.rs |
Validates RAM caps and applies tmpfs size options. |
nvx-rs/src/guest/clone/activation/apply/tests.rs |
Verifies activation records still produce uncapped tmpfs. |
nvx-rs/src/guest/clone/activation/apply.rs |
Preserves activation-record behavior without a cap. |
nvx-rs/README.md |
Documents root settings and capped assembly. |
nvx-rs/public-api/windows.txt |
Records new protocol constants and fields. |
nvx-rs/public-api/linux.txt |
Records protocol and guest cap API additions. |
guest/common/nvx-init-agent |
Reads settings, rejects unsupported modes, and mounts capped tmpfs. |
doc/design/4.1-guest-filesystem-and-image-model.md |
Describes extended tokens and guest mount behavior. |
Review effort: Balanced
|
Dependency update following the revised #1076/#1097/#1098 contracts: PR #1267's per-domain-error foundation is retired. This PR keeps its existing root-token and RAM-upper-cap scope. Move it with the revised #1269 configuration stack so the obsolete error commit is not retained through ancestry. Preserve the shared reader/fixture agreement, strict parsing, tmpfs cap and unchanged boot-contract version; no new error facade or unrelated runtime change is requested. Keep original execution evidence and its head labels. Qualify affected inputs, checks and reviews after the stack update before current-design readiness. No Ready transition or manual CI dispatch is included. |
348d276 to
a625878
Compare
a625878 to
885be7b
Compare
50ded72 to
6f5d7cc
Compare
6f5d7cc to
e5f174b
Compare
|
The dependency update requested above is addressed in e5f174b: its direct parent is #1269's published 885be7b, the pinned qualification baseline is in its ancestry, and retired ed9c38c is not. The single crate Error/Result/ErrorKind contract, unchanged boot version and original root-token/RAM-cap scope are preserved. Complete current-head Windows/Linux checks, fresh native guests and the required WHP/MSHV runs passed; their actual summaries and initial failed attempts are recorded in the updated body. Earlier-head evidence remains separate. The PR is still Draft; no Ready transition or manual CI dispatch was made. |
885be7b to
49d74dd
Compare
e5f174b to
5f0706b
Compare
49d74dd to
3bd7e0d
Compare
5f0706b to
a9add81
Compare
a9add81 to
5b7f85d
Compare
Carry only scripts/test_adversarial.py from #1278 at 5b7f85d. Wait for the fixture's actual live child before timing the workload, accept the existing raw or wrapped timeout, and await Windows fixture teardown. Keep the 0.5-second workload deadline and descendant-exit assertions. Production code and skip guards are unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e19737a6-412b-4652-b67e-b37837521925
Carry only scripts/test_adversarial.py from #1278 at 5b7f85d. Wait for the fixture's actual live child before timing the workload, accept the existing raw or wrapped timeout, and await Windows fixture teardown. Keep the 0.5-second workload deadline and descendant-exit assertions. Production code and skip guards are unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e19737a6-412b-4652-b67e-b37837521925
…pper The boot tokens of a layered root gain four optional settings, after the image's and the upper's: nvx_overlay_mode, trusted-lowers or untrusted-lowers; nvx_scratch_journal, allowed or forbidden; nvx_first_partition_bound, a GPT disk's bound in bytes; and nvx_ram_upper_percent, a RAM upper's cap from 1 to 90 percent of the VM's memory. RootLayout names each as an Option that renders no token when it is None, so that the guest keeps its default. RootLayout::parse reads them strictly: each once, with a value in its decimal or named form, only beside an image, and the journal policy only with a scratch upper, the cap with a RAM upper, and the bound with a GPT image. The shared fixture records the new cases, which nvx-rs and the shell init both replay. guest::root's Upper::Tmpfs takes the cap, mounts the tmpfs with size=N% when it has one, and refuses a cap outside 1 to 90 percent before anything changes, since the kernel reads 0 as no cap. NVX's shell init reads the four tokens with the parser's rules and messages, mounts a capped RAM upper with size=N%, and refuses, before it mounts anything, untrusted lowers and a forbidden journal, which it cannot assemble, as it refuses a GPT image. The sandbox plan names none of the settings, so its command lines stay as they were, and an adapter's plan refuses a setting that its command line names, since it reads the root's tokens back. A clone's activation assembles its tmpfs without a cap, since its record names none, and the runtime adapter's worker hands the cap of its boot tokens to its root. BOOT_CONTRACT_VERSION stays 5, since nothing deployed renders the new tokens. The public API snapshots list the new items. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4e354d65-494d-4fa9-ae9b-4f2e86c3c0f8
Read the root settings before the init refuses GPT images, so an invalid unsigned bound has the same numeric diagnostic in either token order. Keep GPT unsupported and pin the existing image-first overflow fixture's shell diagnostic. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4e354d65-494d-4fa9-ae9b-4f2e86c3c0f8
Track the first upper or setting token so a missing image reports the same parameter as the Rust reader. Pin both existing missing-image fixture diagnostics without changing their cases or accepting invalid roots. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4e354d65-494d-4fa9-ae9b-4f2e86c3c0f8
89d31a7 to
ba9b85f
Compare
eb20dbd
into
feature/nvx-refactor
Carry only scripts/test_adversarial.py from #1278 at 5b7f85d. Wait for the fixture's actual live child before timing the workload, accept the existing raw or wrapped timeout, and await Windows fixture teardown. Keep the 0.5-second workload deadline and descendant-exit assertions. Production code and skip guards are unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e19737a6-412b-4652-b67e-b37837521925
Carry only scripts/test_adversarial.py from #1278 at 5b7f85d. Wait for the fixture's actual live child before timing the workload, accept the existing raw or wrapped timeout, and await Windows fixture teardown. Keep the 0.5-second workload deadline and descendant-exit assertions. Production code and skip guards are unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e19737a6-412b-4652-b67e-b37837521925


Part of #1098: SL2.4a1, the root's kernel tokens carry what
VmConfigsets, and the guest caps a RAM upper.Stacked on #1269 (SL2.3a, in #1097), the configuration parent that #1098 names. The source stack includes
256ebe3dd9c23f20da94503b55d678d1193e392d; its eventual integration destination isfeature/nvx-refactor.What and why
A layered root's boot tokens named only its image and its upper, so a guest couldn't reproduce the overlay mode, the journal policy, the GPT bound or the RAM upper's cap that a
VmConfigsets. This pull request extends the existingprotocol::rootcodec and its shared fixture with these four settings, and the guest mounts a capped RAM upper withsize=N%.RootLayoutgains four optional settings:overlayandjournal, of the activation's typesOverlayModeandJournal;gpt_first_partition_bound; andmax_memory_percent. Each renders one token after the upper's, in this order:nvx_overlay_mode=trusted-lowersoruntrusted-lowers,nvx_scratch_journal=allowedorforbidden,nvx_first_partition_bound=N, from 1 to 18446744073709551615, andnvx_ram_upper_percent=N, from 1 to 90. A layout without settings renders the same two tokens as before, so no producer's command line changes.RootLayout::tokensreturns aVec<String>instead of a[String; 2].RootLayout::parsestays strict and probes nothing. It refuses a setting without a value or repeated, a value that it doesn't know, a number with a sign or leading zeros or out of its range, a journal policy without a scratch upper, a cap without a RAM upper, a bound without a GPT image, and settings without an image, and its messages say what it refuses.guest::root'sUpper::Tmpfsgainsmax_memory_percent: Option<u8>. The assembly refuses a cap outside 1 to 90 with the newError::TmpfsCapbefore any system call, and addssize=N%to a capped tmpfs's options; an uncapped one mounts as before.guest/common/nvx-init-agent, reads the four tokens by the same rules and with the same messages asnvx-rs, and mounts a capped RAM upper withsize=N%. Before it mounts the image, the upper or the overlay, it refuses what it can't assemble: untrusted lowers, since it mounts the overlay only for trusted lowers, and a forbidden journal, since it reads nothing of the scratch's superblock. It still refuses a GPT image, and so every bound, after checking the bound's form and range.Upper::Tmpfswithout a cap, so the activation record, still version 1, and restores mean what they meant. The runtime adapter's worker passes the cap that it parses to its assembly; its plans name no settings and, as before, refuse a command line whose root differs from theirs.BOOT_CONTRACT_VERSIONstays 5, as nvx-rs slimming 2.1.2: the kernel command line and OpenVMM's arguments come from the configuration alone #1098 says, since no released build renders these tokens.MICROVM_ABI_VERSION, the control contract's revision and the activation record's version don't change either.nvx-rs/README.md, the guest file-system design and the runtime adapter's README describe the settings, their defaults and what the init agent refuses.As #1098 says, this pull request doesn't render the whole kernel command line from
VmConfig(SL2.4a2) or carry the cap in the activation record (SL2.5b), and adds no image format, upper kind, overlay mode, journal policy or fallback.protocol::root's crate-privateRAM_UPPER_CAPSrepeats the range of 1 to 90 thathost::configkeeps privately, since the protocol module builds without the host's code.Public API
python scripts/public_api.py --updateadds 8 lines to both snapshots, and 3 more to Linux's; it removes none:protocol::root's token constantsOVERLAY_MODE_TOKEN,SCRATCH_JOURNAL_TOKEN,FIRST_PARTITION_BOUND_TOKENandRAM_UPPER_PERCENT_TOKEN, andRootLayout's fieldsoverlay,journal,gpt_first_partition_boundandmax_memory_percent.guest::root::Upper::Tmpfs's fieldmax_memory_percent, and its hiddenError::TmpfsCapwith the fieldpercent.The snapshots list names, not signatures: code that builds a
RootLayoutwithout the new fields, usesUpper::Tmpfswithout braces, or keepsRootLayout::tokensin an array no longer compiles.Size
222 core lines in 5 counted files, as measured; #1098 estimated 120 in 3, with a stop at 240.
nvx-rs/src/protocol/root.rscounts 138,nvx-rs/src/guest/root.rs31,guest/common/nvx-init-agent46,nvx-rs/src/host/sandbox/plan.rs4 andnvx-rs/src/guest/clone/activation/apply.rs3. Tests add 534 lines, the documentation and the API snapshots 66, and the fixture 41. The runtime adapter'ssrc/worker.rscounts as a test file, but its 8 added lines are built into the adapter's guest agent, which the adapter's VMs run.Before the first commit, a draft measured 244 core lines, past the stop. Instead of stopping to ask, as #1098 requires, it was cut to 202, with the same behavior and tests: one list of the parameters seen serves both the repetition check and the message for settings without an image, the placement messages are shorter, numbers go through
str::parsebehind a check for a sign or leading zeros, and the shell'scasearms take one line each. The one-per-line shell initializers account for 3 further lines; that lint correction is folded into the single logical commit.The unsigned-bound correction adds 17 core lines to that 205-line implementation, for the current 222; it adds no counted file.
Tests
This layer retires or relocates no tests and adds 3 tests and 4 helpers.
python scripts/check_test_changes.py --base 7afa20575c01be01f3cdf148c90d9d9f6c9bed66 --head ba9b85f8eb1fe65dd699ceaf076177ab9ed6c9edreports 43 changes to existing test code and data, 0 relocated unchanged, and 7 added entries. The rebase includes the target's existing bounded adversarial fixture fixes, unchanged; they are no longer modifications in this pull request.tests-modifiedremains set. The root settings, GPT validation-order correction and exact missing-image fixture expectations are retained.The six tests that #1098 names are rewritten, and keep every old case:
protocol::root'swire_values_keep_their_literalskeeps the old literals, and pins the new tokens', their values and the cap's range.every_layout_renders_its_tokens_and_reads_them_backrenders 144 layouts instead of 8: each old one with each setting that its image and upper take, absent or at its bounds. It reads each back from its tokens in order, reversed, and among other tokens.every_reader_parses_the_recorded_command_lines_alikereads every recorded case, old and new, as the exact layout or message that the fixture records, and checks that the cases cover each format, upper, overlay mode, journal policy and setting.the_boot_contract_version_names_these_tokensstill pins(BOOT_CONTRACT_VERSION, MICROVM_ABI_VERSION)at(5, 1)and each image's and upper's tokens, and now each setting's token in its place.guest::root'sboth_overlay_modes_assemble_on_both_uppers_with_their_own_option_setskeeps the scratch and uncapped tmpfs cases, with all their assertions on the mounts and the teardown, and adds caps of 1 and 90 percent: 16 assemblies over both formats and both modes.a_tmpfs_upper_needs_no_scratch_devicecaps its upper at 50 percent and seessize=50%in its options; it still waits for no device, makes no scratch mount point and tears down in the same order.18 more
guest::roottests change only in their upper: each bareUpper::TmpfsbecomesTMPFS, a test constant forUpper::Tmpfs { max_memory_percent: None }, and every input and assertion stays.The 18 tests
The other changes that
check_test_changes.pyreports:settings without a rootanda cap before a RAM upper without an imagefixtures now require the exact shell missing-image messages already recorded for Rust. The shell reports the first upper or setting parameter that needsnvx_layer, preserving token order, invalid-input rejection and the generic refusal for an entirely rootless command line. No fixture case, workload bound or skip is removed or relaxed.a bound past 64 bitsfixture now requires the numeric shell error when the GPT image token precedes its overflowing bound. The shell validates the settings and format-aware bound placement before its existing GPT refusal, matching the numeric diagnosis in either token order; valid GPT images remain unsupported. No case, timeout or skip is removed or relaxed.protocol::root:parsing_reports_bytes_that_are_not_textadds settings whose values aren't text. The helperlayoutgives a layout no settings, andrecorded_layoutreads the four settings that a case names, and refuses a key that it doesn't know.host::sandbox::plan:every_format_and_upper_plans_and_rendersandadapter_plans_render_their_root_and_devices_aloneexpect no settings, and take the two tokens from aVec.host::sandbox'slaunch_contract_builds_agent_command_lineexpects no settings, with the same command line.host::template::plan:openvmms_platform_allowlist_admits_its_tokens_and_refuses_tenant_datarefuses the four new tokens from a tenant too.assembly_inputs_are_taken_exactly_from_the_recordand its helperaccepted_assemblyexpect a tmpfs upper without a cap, and all of the parent's assembly and per-execution-identity assertions stay.scripts/test_nvx_tools.py'sSandboxRootTokenAgentTests:_readprints the cap too;test_agent_reads_the_boot_tokens_as_nvx_rs_reads_themcompares it with the fixture's;test_agent_explains_the_roots_that_it_cannot_assembleadds untrusted lowers and a forbidden journal; andtest_agent_mounts_the_upper_that_the_tokens_nameadds a tmpfs mounted withsize=25%.each_class_of_failure_ends_the_clone_with_its_own_statususes a tmpfs upper without a cap. Inguest_session, the helperlayoutnames no settings,the_worker_assembles_an_image_over_ram_in_the_overlay_mode_of_its_configurationpasses a cap of 25 percent to the assembly, andthe_worker_assembles_a_gpt_disk_with_the_first_partition_bound_of_its_configurationstill takes its bound from the adapter's configuration.layered_launch'sa_layered_launch_plans_its_blocks_and_boot_tokens_with_the_adapters_planexpects no settings.nvx-rs/tests/fixtures/root-tokens.jsonkeeps its 79 cases, of which only the last one's closing comma changes, and adds 39, 118 in all; its description says what the init agent can't assemble.Added:
guest::root'sa_tmpfs_upper_refuses_a_cap_that_is_not_1_to_90_percent: caps of 0, 91 and 255 fail before any system call.host::sandbox::plan'sadapter_plans_refuse_the_settings_of_their_root_that_they_do_not_name: an adapter plan refuses a command line whose root has settings that the plan doesn't name.SandboxRootTokenAgentTests.test_agent_refuses_the_settings_with_the_messages_of_nvx_rs: 11 refusals of the init agent, each withnvx-rs's message.TMPFS, inguest::root's tests, andLAYOUT_KEYS,contracted_overlayandcontracted_journal, inprotocol::root's.Every other test of these modules is kept as it was: among them,
protocol::root'sthe_longest_tokens_are_those_of_an_identified_image_over_scratch, which still finds the longest image and upper tokens at 70 and 27 bytes, as launches' command-line budgets count them; 44 ofguest::root's original 64 tests; the other sandbox-plan and activation tests; and the other shell root-token tests.The shell also checks the unsigned bound's upper limit before placement: twenty-digit values are compared a digit at a time, never through signed whole-value arithmetic. Four added shared cases require both readers' exact numeric or placement errors for overflow, an extra digit, the maximum and a lower twenty-digit value.
The follow-up at
89d31a7efc883e00a5b6579d991d9a793a2a2060was separately qualified: the entire eight-test shell reader class and both Rust shared-reader tests passed on Windows and Linux, with the applicable quality checks. All three affected guest images were freshly assembled with current input manifests. Each WHP/MSHV qualification passed the complete three-case missing-image/RAM probe, all 13 RAM-cap probes, six Alpine and three Ubuntu selections, and 156 canonical adapter scenarios across 11 targets. The initial task probe's device-plan refusal was preserved; the complete corrected three-case probe passed with unchanged messages and bounds. Unchanged compiled inputs were certified against their original builds, not relabelled as new builds or executions. TheValidationsection retains the original recorded qualification. The current total is 234 core lines in the same five counted files, below the 240-line stop.The current
ba9b85f8eb1fe65dd699ceaf076177ab9ed6c9edcarry onto7afa20575c01be01f3cdf148c90d9d9f6c9bed66is mechanical: README context and formatting-only fixture conflicts retain both branches' behavior. Current local checks passed: 29 Windows and 38 Linux Rust/adapter command entries, all 15 quality commands, 1818 Windows unit tests (67 platform skips), 1818 Linux unit tests (35 platform skips), and all 84 adversarial cases on each platform, with the existing 11 Windows and zero Linux skips. The original half-second workload bound, real readiness and descendant-exit checks remain unchanged. No new native guest-image/VM matrix or independent pre-push review was required for this simple rebase; the earlier executions above retain their original source and runtime identities. The current total remains 234 core lines in the same five counted files.Validation
Source head
e5f174b39864938f6fe50768ad3205b10128034a, direct parent885be7ba1896f5dacb5f2fc7fbf53ecb15f7a847.The parent contains the pinned qualification baseline
256ebe3dd9c23f20da94503b55d678d1193e392d. The retired per-domain-errorfoundation is absent; the crate keeps its single
Error,ResultandErrorKindcontract.The complete applicable action commands passed at this head. These are the
runners' own summaries, not just enclosing exit codes. Earlier-head
executions remain separate and are not counted as current-head evidence.
check-nvx-rsvalidate-nvxunit suitecheck-qualityRust qualification covers 29 Windows and 41 Linux command entries, with
successful target summaries for all 16 complete Cargo test commands. The
selections overlap; nested child summaries do not add distinct coverage.
Bundling runs with
NVX_BUNDLEunset. Python CLI checks select theexact-source release CLI through
NVX_RS. Windows uses native CPython3.14.9 and Git's
usr/bin/sh, and the canceled-capture shell test executesrather than skipping. Linux's complete Python suite runs in private
PID/mount namespaces, retaining the original UID/GID/groups and a shell as
PID1. The pinned ShellCheck and shfmt binaries run natively. Quality
applicability follows
.github/workflows/ci.yml: quality is Linux-only;Rust and CLI checks cover both systems.
All ten Rust guest executables were validated against this head. Alpine
and Ubuntu initramfses and the Ubuntu EROFS layer were freshly assembled
natively from those executables, with matching source provenance. The
actual Alpine archive's init scripts and all ten executables were checked
byte-for-byte or by hash. Only the hash-verified kernel, configuration,
scratch template and native OpenVMM binaries came from the pinned
baseline: OpenVMM
9cec2b89245f41f2126f9a787c5e43a0d84b7154,kernel 6.18.38 SHA-256
692ac2a9f351aa5a31dcf1810782d82c1a83a0f1fd0b3edcf4fad9c0d94cfe81.Consumed runtime and guest hashes remained unchanged across both runs.
On each backend, the direct probes measured uncapped tmpfs and caps of
1, 25 and 90 percent using the kernel's page arithmetic. Eight invalid or
unsupported settings, including both unsigned-bound overflows, were
refused before mounting or running the workload; the permitted
scratch/journal combination booted. Public plans still emit no new
settings. Rust's capped assembly path is covered by the syscall tests;
the direct VM probes exercise the init script and kernel.
Initial failed attempts are retained, not counted as passes. A Linux
compilation stopped on a disk emergency; the complete batch was retried
after space became available. Its host-configuration test then encountered
stale root-owned fixtures from an earlier adapter execution. Those owned
generated fixtures were quarantined, and the entire unchanged
host-configuration test command passed. The first Windows adversarial
command failed two short process-startup cases. Focused baseline/head
comparisons also failed and were diagnostic only; the entire unchanged
84-case module subsequently passed standalone after other owned work
finished. No deterministic timing cause, test relaxation or focused-rerun
waiver is claimed.
An independent read-only review of this exact head was clean, including
220 unsigned-bound/error-order probes and all 118 shared fixture cases.