Repository navigation
nvx-rs: stop reading the activation record's template and workload identity - #1279
Conversation
This pull request changes existing testsIt modifies or removes 33 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: 3. Compared the merge base |
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues remain for the changes preparing NVX for activation record v2.
No open issues 🎉
What changed in this PR
Prepares NVX for activation record v2 under #1101 by removing consumers of the record’s template reference and workload identity, while leaving the v1 schema and codec unchanged.
Changes:
- Removes the activation-record template comparison during reattach, retaining existing clone-record validation.
- Removes workload identity checking and propagation from guest activation; runtime-adapter clone executions must name their own identity.
- Updates regression tests, documentation, scenario names, and the Linux public API snapshot.
| File | Description |
|---|---|
tests/runtime-adapter/tests/vm_clone.rs |
Updates VM scenarios for explicit execution identities. |
tests/runtime-adapter/tests/guest_session.rs |
Updates fake activation and execution-identity assertions. |
tests/runtime-adapter/src/worker/activation/tests.rs |
Removes the retired account-error status case. |
tests/runtime-adapter/src/worker/activation.rs |
Starts clone sandboxes without a sandbox-wide identity. |
tests/runtime-adapter/src/worker.rs |
Changes activation results to return no identity. |
tests/runtime-adapter/src/clone.rs |
Documents that guests ignore the recorded workload identity. |
tests/runtime-adapter/src/bin/runtime-adapter-host.rs |
Updates clone-start option documentation. |
tests/runtime-adapter/scenarios.txt |
Registers the renamed clone identity scenario. |
tests/runtime-adapter/README.md |
Documents revised activation behavior and scenarios. |
nvx-rs/src/host/template/clone/tests/launch.rs |
Tests healthy reattach and mismatched failure records. |
nvx-rs/src/host/template/clone/reattach.rs |
Removes the activation-record template comparison. |
nvx-rs/src/host/template/clone.rs |
Updates CloneRequest::workload documentation. |
nvx-rs/src/guest/clone/activation/apply/tests.rs |
Revises activation tests for ignored workload identities. |
nvx-rs/src/guest/clone/activation/apply.rs |
Removes account checks, identity results, and account errors. |
nvx-rs/src/guest/clone.rs |
Documents execution identities independently of activation. |
nvx-rs/src/activation/mod.rs |
Clarifies the retained v1 workload identity fields. |
nvx-rs/README.md |
Updates the guest activation description. |
nvx-rs/public-api/linux.txt |
Removes the identity accessor and account-error entries. |
Review effort: Balanced
This comment has been minimized.
This comment has been minimized.
A reattach refused the clone's activation record unless its restore section named the template of the clone's lease. As decided for #1101, the reattach leaves that to the clone's records: it already matches them with the lease through read_failure_of, which refuses the record of another template's failure. read_activation still requires the canonical encoding of the record of a restore, with the same fault. The new test a_reattach_fails_a_clone_whose_record_of_a_failure_is_of_another_template takes over a healthy clone without a failure record, then refuses, as another clone's failure, a failure record of another leased generation, and one of another key of the leased generation. Part of #1101. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c2e22d0d-c583-4401-a947-31781d4fec41
A clone's activation record may carry a workload identity, whose account nvx::guest::clone::apply checked in the assembled root, and as which every execution of the clone then ran. A clone now has no workload identity of the whole sandbox: the application ignores the record's, and each execution names its own identity, as in a sandbox whose root configuration names none, so a start that names none is refused with EINVAL. A cold sandbox's workload identity is unchanged. - apply: Applied loses identity(), into_parts its identity, and ApplyError its Account variant. The entry that binds the runtime's files, through which no execution runs, takes the user and group ID 0. - The runtime adapter's worker starts a clone's sandbox without a workload identity. Machine ID and view failures keep the status ACTIVATION_WORKLOAD. - The record, its codec and validation, and the host program's --workload-identity keep the identity; their docs say that the guest ignores it. - The Linux public-API snapshot loses the four lines of Applied::identity and ApplyError::Account. The guest's apply tests and the adapter's guest_session and vm_clone tests drop the account refusals of a record's identity, and their clones' executions name their identities. Part of #1101. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c2e22d0d-c583-4401-a947-31781d4fec41 Copilot-Session: 383380be-1df9-4f25-bf77-97c24b2eb591
36fa883 to
a81617a
Compare
|
Neuro-formal verification (NFV) selects nontrivial properties from PR intent and source, tries verification, then tries refutation. NFV: 0 refuted, 5 verified in PR #1279PR head These are results for translated models under their stated assumptions, not proofs of source-code correctness or confirmed source bugs. refuted: 0 · verified: 5 · resources exhausted: 0 · type-check/audit failure: 3 P1Intended property: Invalid proposed property Outcome: type-check/audit failure DiagnosticProperty lacks an exact PR/commit evidence quote P2Intended property: If activation completion succeeds, settle launches executions in a sandbox with no sandbox-wide identity, using the applied hostname and publication; it records the applied mappings and returns exactly 1 + joined Ready messages. Outcome: type-check/audit failure DiagnosticWitness arity exceeds supported bounds P3Inferred property: If activation completion fails, settle takes the failure-ending path rather than marking the timeline ready, launching executions, installing a mapping order, or returning Ready messages. Outcome: verified The property was verified for the translated model. Verification detailsCaptured source (inferred property): Err(failure) => self.end(executions, &failure), Assumptions: No additional input restrictions. Environment models: Represents the omitted Activation::complete result using the explicit completionSucceeds input.; Clears executions and returns the failure-ending step without Ready messages.; Constructs the Ready message used by the successful continuation.; Records the observable occurrence of executions.run_in in the successful branch. Checked property: ensures appliedPresent && !completionSucceeds ==> (tookFailureEndingPath && !timelineReady && !executionsLaunched && !mappingOrderInstalled && !readyMessagesReturned)Evidence: The full verified Dafny is retained in the linked run artifact. P4Inferred property: If clone application fails, propagate its error without changing the worker's export or root. If it succeeds, move the applied export into the worker, retain the remaining applied clone as its assembled root, and return success. Outcome: verified The property was verified for the translated model. Verification detailsCaptured source (inferred property): let mut applied = clone::apply(record, template, deadline)?; Assumptions: No additional input restrictions. Environment models: Represents clone::apply as propagating its supplied error on failure or returning an Applied value with the supplied export and remaining clone state on success.; Moves Applied.export to the worker while retaining the other Applied fields represented by the clone-state token. Checked property: ensures ((!cloneSucceeded ==> (!resultSucceeded && resultError == cloneError && finalExportPresent == oldExportPresent && (oldExportPresent ==> finalExport == oldExport) && finalRootPresent == oldRootPresent && (oldRootPresent ==> finalRoot == oldRoot))) && (cloneSucceeded ==> (resultSucceeded && finalExportPresent == appliedExportPresent && (appliedExportPresent ==> finalExport == appliedExport) && finalRootPresent && finalRoot == appliedClone)))Evidence: The full verified Dafny is retained in the linked run artifact. P5Intended property: Given a readable canonical restore activation record and successful preceding operations, reattachment does not reject before invoking the VM reattach operation merely because the activation record’s template reference differs from the clone lease; failure-record matching remains the mechanism for detecting another template’s clone failure. Outcome: type-check/audit failure DiagnosticThe requested corrected full tagged Dafny model exceeds the available response budget; returning a truncated model would violate the requirement to preserve the complete source slice and property. P6Intended property: For any successfully read and decoded activation record, acceptance depends only on its bytes being the canonical encoding and on it containing a restore section; its template reference is not compared with a clone lease. Outcome: verified The property was verified for the translated model. Verification detailsQuoted intention: Reattaching a clone no longer compares the activation record's template with the clone's lease. Assumptions: Every integer representing a byte read from the activation record is in the Rust u8 range.; Every integer representing a byte produced by ActivationRecord::encode is in the Rust u8 range. Environment models: Models the filesystem open/read chain using explicit abstract inputs for its success and returned bytes.; Models ActivationRecord::decode using explicit abstract inputs for decode success and the decoded template reference.; Returns the explicit abstract canonical encoding of the successfully decoded activation record.; Returns whether the decoded activation record contains its optional restore section. Checked property: ensures readSucceeded && decodeSucceeded ==> (accepted <==> (canonicalEncoding == bytes && hasRestore))Evidence: The full verified Dafny is retained in the linked run artifact. P7Intended property: For any root and view, entry builds the runtime-binding Entry with UID/GID 0:0, the view’s hostname and machine ID, and adds the view’s resolver only when one is present. Outcome: verified The property was verified for the translated model. Verification detailsQuoted intention: The private `View::entry`, through which the guest binds the runtime files, such as the generated machine ID and the resolver file, now builds its `Entry` with the identity `0:0`. Assumptions: No additional input restrictions. Environment models: Projects Entry::new as retaining root, selecting UID/GID 0:0, and initially carrying no resolver.; Models the hostname builder as storing the cloned view hostname.; Models the machine-ID builder as storing the cloned view machine ID.; Models the resolver builder as marking a resolver present and storing its cloned path. Checked property: ensures entryRoot == root && uid == 0 && gid == 0 && entryHostname == hostname && entryMachineId == machineId && entryHasResolver == resolverPresent && (resolverPresent ==> entryResolver == resolver)Evidence: The full verified Dafny is retained in the linked run artifact. P8Inferred property: For a Failed activation cause, failure_status returns ACTIVATION_ROOT when the contained error is not one of the recognized ApplyError categories; by contrast, an otherwise unrecognized outer ActivationCause returns ACTIVATION_PROTOCOL. Outcome: verified The property was verified for the translated model. Verification detailsCaptured source (inferred property): // A failure of the application that this build does not know. Assumptions: Each activation status constant is a Rust u8 value. Checked property: ensures (cause == 8 && applyError != 0 && applyError != 1 && applyError != 2 && applyError != 3 && applyError != 4 && applyError != 5 ==> status == ACTIVATION_ROOT) && (cause != 0 && cause != 1 && cause != 2 && cause != 3 && cause != 4 && cause != 5 && cause != 6 && cause != 7 && cause != 8 && cause != 9 && cause != 10 ==> status == ACTIVATION_PROTOCOL)Evidence: The full verified Dafny is retained in the linked run artifact.
Add this agentic workflow to your repoTo install this agentic workflow, run |
Part of #1101 (SL2.5a: no consumer outside the codec and its schema reads what record v2 drops).
Stacked on #1277, the follow-up of SL0.4 (#1261, in #1091) whose public-API snapshots this pull request updates: #1277's head,
cfd105a9, is this pull request's base. GitHub retargets this pull request todevonce #1277 merges.What and why
Record v2, in SL2.5b, drops among other fields the activation record's template reference and workload identity. Two readers of those fields remain outside the codec, both in the
nvxcrate: the host's reattach of a clone, and the guest'sapply. This pull request removes those two reads, so that SL2.5b can change the codec without keeping fields for them. The runtime adapter, a test program, follows the smaller result.nvx-rs/src/host/template/clone/reattach.rs). Reattaching a clone no longer compares the activation record's template with the clone's lease. The clone records check it instead:read_failure_of(records.rs), which the reattach already calls under the store's lock, refuses a failure record whose store, lease, generation, key or launch differs. A healthy clone, which has no failure record, is taken over as before. The activation record must still be the canonical encoding of a restore's record, or the reattach fails with the same host fault as before. Leases, clone records and their matching rules don't change.nvx-rs/src/guest/clone/activation/apply.rs). The guest'sapplyno longer extracts the record's workload identity, checks its account in the image or passes it on.Appliedloses its identity andApplied::identity, andinto_partsreturns(Root, RuntimeBinds).ApplyError::Account,System::accountand the account check go with them.View::entry, through which the guest binds the runtime files, such as the generated machine ID and the resolver file, now builds itsEntrywith the identity0:0. That entry validates its path, IDs, capabilities and hostname and binds those files. It prepares no execution, and0:0is no default identity of a sandbox or an execution.tests/runtime-adapter/src/worker.rsandworker/activation.rs).RootOperations::applyreturnsResult<(), ApplyError>.EINVAL.VERSIONstays 1, and the schema, the codec, the canonical fixtures and the record writers don't change.TemplateReference,StoreGenerationandactivation::Identitystay for the old stores.start --workload-identityandrun --workload-identitystill accept the identity and record it in the activation record; the guest now ignores it when it applies the record, and SL2.5b drops it from the record.activationmodule's docs ofWorkloadView, itsidentityfield andWorkloadIdentity, and the doc comment ofCloneRequest::workload;clone::Request::workload_identity, itsclonemodule and its host program'sstart;vm_clone's module and itsNOBODY;clone::Request's paragraph, its status table,start --workload-identity, the guest-session fake root, and the rows of the three rewrittenvm_clonescenarios.python scripts/public_api.py --updateregenerated the public API on Linux, which loses 4 lines; the one on Windows doesn't change:Not in this pull request, as #1101 says:
TemplateReference,StoreGenerationoractivation::Identity, which the old stores use until SL6.1.Tests
python scripts/check_test_changes.py --base cfd105a9797fce81d0b72ee5b9ccf3700d2f1d2ereports "33 change(s) to existing tests; relocated unchanged: 0; added: 3".apply, or from the reattach's check moving to the clone records.Added, in
nvx-rs/src/host/template/clone/tests/launch.rs:a_reattach_fails_a_clone_whose_record_of_a_failure_is_of_another_template:nvx-rs/src/guest/clone/activation/apply/tests.rs, 18 tests, which run on Linux only:a_workload_identity_without_an_account_in_the_image_is_refused_before_ready.the_application_checks_no_account_of_the_record_s_workload_identityreplaces it: all 7 systems, whatever accounts their images hold, reach Ready, with the assembly and the view's bind as the only effects.assembly_and_identity_inputs_are_taken_exactly_from_the_recordbecomesassembly_inputs_are_taken_exactly_from_the_record.an_export_or_a_network_that_fails_fails_the_activation_once_the_root_is_torn_down.application_errors_name_their_cause.Accountcase goes.AlreadyExists.a_valid_activate_activates_the_root_and_the_workload_once_before_ready;a_gpt_disk_s_partition_table_is_reread_before_its_root_is_assembled;a_generated_machine_id_is_generated_once_after_the_gate_for_every_execution;the_view_s_entry_takes_the_record_s_hostname_and_machine_id;a_machine_id_or_a_view_that_fails_fails_the_activation_once_the_root_is_torn_down;an_export_is_mounted_through_the_template_s_device_and_handed_to_the_adapter;a_network_s_dns_server_is_written_to_a_resolver_file_that_every_execution_sees;a_network_that_publishes_the_workload_s_listeners_starts_their_announcer_to_its_gateway;a_generated_machine_id_and_a_resolver_file_keep_the_root_busy_until_their_binds_are_removed, which needs root.no_mount_spawn_or_id_generation_happens_before_the_gate, whose shared expectations lose only the account;a_failed_assembly_is_final_and_reads_and_tears_down_nothing;the_runtime_s_binds_are_removed_before_the_root_is_torn_down;a_resolver_file_names_its_dns_server_once_and_every_execution_may_read_it;the_application_refuses_without_privileges_what_it_cannot_assemble.EffectlosesAccount;view()andactivated()lose the identity, andfull_view(),applied_everything()and the imports follow;Refusal,FakeSystem::accountandworkload(), which nothing uses any more, go.The runtime adapter:
worker::activation::tests::each_class_of_failure_ends_the_clone_with_its_own_status(src/worker/activation/tests.rs): itsApplyError::Accountcase goes. Its machine-ID and view cases, and the other status classes, stay.tests/guest_session.rs:applychecks no account and returnsOk(()).a_clone_s_activation_fails_finally_with_the_status_of_the_failure_s_classloses only its two cases of a record whose workload identity has no account,4242andORPHAN.ORPHANstays for the checks of explicit executions.a_clone_s_executions_run_as_the_workload_identity_and_with_the_hostname_of_its_record.a_clone_s_executions_name_their_own_identity_and_take_the_hostname_of_its_recordreplaces it: for a record with a workload identity and for one without, an execution that names no identity fails withEINVAL, and one that names an identity runs with the record's hostname.a_clone_reports_when_its_events_happened_after_the_acknowledgementanda_clone_maps_its_record_s_table_from_its_export_and_leaves_the_proxy_to_the_hostnow name their executions' identity. Their ordering, mapping and teardown assertions stay.tests/vm_clone.rs, which boots VMs:a_clone_whose_activation_fails_ends_before_it_is_readyloses only its case of a start with a workload identity whose account the image lacks, with that identity's constant,STRANGER. Its unusable scratch, forbidden journal and three protocol refusals stay.a_clone_runs_its_executions_as_its_workload_identity_once_it_is_readybecomesa_clone_runs_the_executions_that_name_their_identity_once_it_is_ready.--workload-identity 65534:65534, nobody's, which the record keeps.--identity 65534:65534runs as 65534/65534, and one without--identityfails withEINVAL.a_run_runs_one_execution_in_its_clone_then_tears_the_clone_down: the run no longer passes--workload-identity, its execution names--identity 65534:65534, and its refused execution names none. Its environment, directory, status and output assertions stay.tests/runtime-adapter/scenarios.txtand the README's scenario rows follow the rename.the_worker_checks_the_account_of_the_sandbox_s_workload_identity_in_the_assembled_root, intests/guest_session.rs;vm_layered'saccountless_workload_identity_fails_its_stageandexecutions_run_as_the_workload_identity;vm_clone'sa_clone_whose_view_cannot_be_set_ends_before_it_is_ready;existing_accounts_are_read_below_the_rootandaccount_databases_are_bounded_regular_files_reached_through_no_link.Size
9 core lines in 2 counted files, as measured under #1076's rules: 5 in
apply.rsand 4 inreattach.rs. #1101 estimated 80 core lines in 2 files, with a stop at 160.tests/runtime-adapter/, as tests, although its worker changes change how that test program behaves.The 18 changed paths, by the measure's category
nvx-rs/README.mdnvx-rs/public-api/linux.txtnvx-rs/src/activation/mod.rsnvx-rs/src/guest/clone.rsnvx-rs/src/guest/clone/activation/apply.rsnvx-rs/src/guest/clone/activation/apply/tests.rsnvx-rs/src/host/template/clone.rsnvx-rs/src/host/template/clone/reattach.rsnvx-rs/src/host/template/clone/tests/launch.rstests/runtime-adapter/README.mdtests/runtime-adapter/scenarios.txttests/runtime-adapter/src/bin/runtime-adapter-host.rstests/runtime-adapter/src/clone.rstests/runtime-adapter/src/worker.rstests/runtime-adapter/src/worker/activation.rstests/runtime-adapter/src/worker/activation/tests.rstests/runtime-adapter/tests/guest_session.rstests/runtime-adapter/tests/vm_clone.rsValidation
At this head,
36fa8834, on a Windows x86-64 host with WHP and on a Linux x86-64 host with MSHV (Azure Linux 3, kernel 6.18.34.mshv3-1.azl3, AMD EPYC 9V74), with Rust 1.95.0 and the MSRV, 1.89. CI hasn't run on this head, and nothing was published.check-nvx-rson Windows: the 29 steps that apply there pass.cargo fmt.cli, and withtestingbut no default features; and for-p nvx's library without default features.--listcheck that the new reattach test runs.python scripts/public_api.pyand the MSRV check.x86_64-unknown-linux-musl, of nvx-rs with all features and of the adapter's guest configuration.check-nvx-rson Linux: every step passes.--listcheck.applytests by name: 17 passed and 1 ignored, the root-only test below.python scripts/public_api.py, whose listings equal the committed snapshots byte for byte.check-guest-binaries, and the guest binaries built and tested.applytest:a_generated_machine_id_and_a_resolver_file_keep_the_root_busy_until_their_binds_are_removedneeds root and Linux 6.8 or later, and CI doesn't run it.--nocapture.validate-nvxandcheck-qualityon Linux: all pass:scripts/test_adversarial.py(84 run, none skipped);test_hosts.py, the CLI's help, and the OpenVMM vectors and CPU profiles;nvx.py test-runtime-adapter --check-only;validate-nvxandcheck-qualityon Windows: 27 of 28 steps pass:scripts/test_adversarial.pywith CPython 3.14.9 (84 run, 11 of them skipped there as Linux-only);test_hosts.py, the CLI's help, and the OpenVMM vectors and CPU profiles;nvx.py test-runtime-adapter --check-only("156 scenarios name exactly the tests of 11 targets") and the test-change check.test_ciimportsfcntl, which Windows lacks (ModuleNotFoundError: No module named 'fcntl'), as at nvx: hide NVX's programs from the public API #1265's head. They pass on Linux, wherecheck-qualityruns them.python scripts/nvx.py test-runtime-adapterpassed "the 156 scenarios of 11 targets" on MSHV and on WHP.vm_clone's 35: the three rewritten ones by name, the view refusal and the cold path's identity scenarios.test-microvmsandbox scenarios,managed-exec-config,sandbox-lifecycle,sandbox-proxy-onlyandsandbox-ram-upper, passed on both./dev/mshvis root-only on that host, with executables that the user's account built beforehand.d436960d...over 146 files (2ab01e37...at the base).5aa8b028for MSHV and for WHP.nvx-rs: on Linux, built with Cargo before the runs; on Windows, built bynvx.pywhen the Python unit tests needed it, and reused by the WHP runs.--check-onlyon Linux and by the Rust checks on Windows, and reused by the runs.build/nvx-rs.provenance.jsonduring the runs above.nvx.py test-adversarialand packaging do, while the runtime identity that each sandbox records reads only the OpenVMM, kernel and initramfs records.release.validate_runtime_artifact_provenancepassed on Windows and failed on Linux for the missing record.python scripts/nvx.py record-nvx-rs-provenancethen recorded the unchanged Linux CLI: its bytes, size and times, and the sources, were the same before and after. The validators then passed: the OpenVMM, kernel, initramfs and CLI checks, and the whole runtime check.cargo test --features bundled(26 s).nvx.pystarted in the Python unit tests (26 s).cargo check;test-microvm's other scenarios; the benchmarks. CI runs them.devpush run 41783992397 failedNVX microVM tests / Linux / KVM / Guests and sandboxes.vm_clonescenarioone_template_serves_the_clones_of_every_network_policy_at_its_guest_s_address, whose execution reply readlistener 124, a timeout, attests/runtime-adapter/tests/vm_clone.rs:1635.