Skip to content

nvx-rs: terminate VMs of another runtime identity on stop - #751

Merged
Pedro Henrique Penna (ppenna) merged 4 commits into
devfrom
esaurez/a4-pr6-runtime-identity-20261021
Oct 23, 2026
Merged

Pedro Henrique Penna (ppenna) merged 4 commits into
devfrom
esaurez/a4-pr6-runtime-identity-20261021

Conversation

@esaurez

@esaurez Enrique Saurez (esaurez) commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

A sandbox that another runtime started (another OpenVMM, kernel, initramfs, or boot or control contract) is never driven through a guest protocol that it might not speak. Until now its owner could not stop it either: the CLI and host::instance reported it and asked for the artifacts that had started it. This PR lets the owner stop it by identity-verified forced termination, and versions the boot contract, so that the runtime identity changes with the boot tokens.

Termination

  • Supervisor: Vmm::terminate(locked, bound) takes the forced step alone, through the VMM's verified process identity (a pidfd on Linux, a process handle on Windows), with no graceful step, and waits up to the caller's bound. It returns Forced, Graceful if the VMM had already exited, or TimedOut. It clears the runtime record once the VMM has exited. A VMM that still runs, or whose state cannot be determined, keeps its record, so that a later call retries. Bounds of zero or above MAX_TIMEOUT are refused. Vmm::stop keeps its behavior and now shares its helpers with terminate.
  • host::instance: LockedInstance::terminate(mismatched, timeout) ends a Found::Mismatched VM and returns Stopped { forced, report: None }: it reads no outcome report, which only the other runtime's OpenVMM could have written. It fails, naming the process, if OpenVMM did not exit in time. Mismatched::error points at it.
  • CLI: stop of a sandbox whose VM runs under another runtime identity terminates its OpenVMM within --timeout, exits 0, and warns on standard error. The termination itself waits one retry interval at most, and the stop's own wait, which Ctrl-C ends, waits for the rest; a stop that Ctrl-C ends before OpenVMM's exit is confirmed keeps the runtime record for the next stop. start and exec refuse such a sandbox, naming the parts that differ, and point at stop, and deprovision refuses it as running. stop of a sandbox of the same identity is unchanged: graceful, and it reports a timeout without forced termination.
  • aci_edge_sandboxes: stop terminates such a VM and reports it as forced. Every other operation still fails with backend_error.

A record whose process reference names a process that has exited, as after a reuse of its ID or on a later host boot, is found exited before any identity comparison, so nothing is signaled. An interrupted launch is recovered as before, whatever the identity.

Boot contract version

The runtime identity recorded MICROVM_ABI_VERSION, 2, as its boot contract, so it did not change when #676 made the layered root's boot token name the image format and added the RAM upper. nvx::contract::BOOT_CONTRACT_VERSION, now 3, versions the boot contract: the microVM ABI, the kernel command-line tokens that configure the guest, and the root that the guest assembles from them. nvx::contract documents when each of MICROVM_ABI_VERSION, BOOT_CONTRACT_VERSION, CONTROL_SESSION_PROTOCOL_VERSION, and CONTROL_CONTRACT_REVISION must change. A test in protocol::root pins the version beside every token form of a layered root, and its exhaustive matches stop compiling when a format or upper is added. MICROVM_ABI_VERSION itself is unchanged, since releases declare it in their SOURCE-MANIFEST.json.

The artifact identities still come from install or staging metadata, a release's SHA256SUMS or a checkout's build provenance, and nothing is hashed at launch.

Intended golden and message changes

  • nvx-rs/tests/fixtures/cli.json: the 26 runtime.json snapshots, in 13 cases, record "bootContract": 3 instead of 2. No other golden changes.
  • nvx-rs/tests/fixtures/store/*.json: "bootContract": 3.
  • CLI start and exec of a mismatched sandbox: sandbox runs under another runtime identity than these artifacts, which differ in PARTS, so it is not driven: stop it to terminate its OpenVMM process PID without a guest shutdown, which replaces ... which differ in PARTS: stop it with the artifacts that started it (OpenVMM process PID).
  • CLI stop of a mismatched sandbox: status 0 and warning: sandbox ran under another runtime identity than these artifacts, which differ in PARTS: its OpenVMM process PID was terminated without a guest shutdown, instead of the error above.
  • Mismatched::error, which aci_edge_sandboxes reports as backend_error: sandbox NAME runs under another runtime identity, which differs in PARTS, so it is not driven: stop it to terminate its OpenVMM process PID without a guest shutdown, which replaces ... stop it with the NVX build and artifacts that started it (OpenVMM process PID).

Tests

  • Supervisor: a termination takes only the forced step; undetermined states are errors; refused bounds; a second termination finds the VMM exited; on Windows, a VMM that the caller cannot terminate keeps its record, and the next termination clears it.
  • The fake OpenVMM counts the control connections that it accepts, in fake-openvmm-TOKEN-sessions.log beside its kernel, so the instance and CLI tests prove that a termination opened no session.
  • host::instance: a restarted orchestrator of another identity terminates the VM without a session; an outcome report beside such a VM is never read, whether the stop terminates OpenVMM or OpenVMM exited after it was found; records that name another process, by start time or boot, end nothing.
  • CLI: another identity (no session, OpenVMM ended, the state's entries); an identity that cannot be determined (missing build provenance keeps the record, and the next stop terminates the VM); records of an earlier boot or a reused process ID (stale, nothing signaled); an interrupted launch under another identity. Unit tests of the termination's wait, with an OpenVMM that a test scripts: one that outlives its termination is terminated with a bound of one retry interval, whatever the timeout, and Ctrl-C ends the wait for it and keeps its record; one that exits during the wait is reported as terminated, and its record goes; one that still runs at the deadline keeps it.
  • aci_edge_sandboxes: the fake test follows the new stop, whose metadata holds only forced even beside an outcome report; the new real-hypervisor test a_vm_of_another_runtime_identity_is_only_terminated checks that a restarted backend reattaches, and that one whose initramfs differs refuses exec and terminates the VM on stop, after which the sandbox starts again.
  • sandbox-lifecycle (real VMs): a CLI under an artifact root whose build provenance names another initramfs refuses exec, start, and deprovision while the checkout's CLI still drives the VM, and its stop terminates OpenVMM and leaves only config.json, sandbox.lock, and openvmm.log; the sandbox then starts again.
  • Store: records of boot contract 2 are mismatched.

The test adapter's restart scenarios (#718) cover the same-identity restart on the host side, so this PR reuses them, unchanged, rather than adding another: a later process of the adapter's host program reattaches to the VM, which is not its child, in a later session epoch, and a third one stops it gracefully. Their other scenario checks that a host program that ships another build of the guest agent finds the VM mismatched, in its initramfs, and signals nothing. The adapter's stop still signals nothing to a mismatched VM, which is its own policy; having it terminate one through Vmm::terminate is left to the test adapter's end-to-end work in #622.

Part of #622
Refs #620, #535

Validation

On this head, with the artifacts of the development releases v0.1.0-dev.872ef6a1bbdf on Windows and v0.1.0-dev.03d945bc2d3a on Linux, whose OpenVMM matches this branch's gitlink:

Windows 11 x64, WHP:

  • nvx-rs, aci_edge_sandboxes, and tests/runtime-adapter (the protocol, and the host feature), as check-nvx-rs and check-aci-edge-sandboxes run them: cargo fmt --all --check; Clippy with -D warnings for all targets and features, and for the library without default features; cargo test --locked with all features and with the defaults, and the library's build and tests without default features; cargo doc with -D warnings; cargo +1.89 check; the artifact-bundling test against a staged synthetic release; and python scripts/nvx.py test-agent-bundling --toolchain 1.95.0. All pass.
  • validate-nvx's Windows steps, except its pip install: compileall, the unittest suites, test_adversarial.py, test_hosts.py, and the --help checks, with Git's bash first on PATH for the setup suite's Linux cases. All pass.
  • ruff check, ruff format --check, and scripts/check_markdown_links.py.
  • python scripts/nvx.py test-microvm --backend whp --scenario lifecycle --scenario managed-lifecycle --scenario sandbox-lifecycle --scenario managed-exec-config --scenario sandbox-proxy-only: pass.
  • python scripts/nvx.py test-aci-edge-sandboxes --backend whp: 19 passed.
  • The test adapter's VM scenarios, cargo test --locked --features host -- --ignored in tests/runtime-adapter, with NVX_HYPERVISOR=whp, the checkout as NVX_ARTIFACTS_DIR, and a guest agent built on Linux: 14 passed, the two restart scenarios and the twelve layered ones.

Linux x86_64 with the MSHV kernel, as a non-root user:

  • Every Linux step of check-nvx-rs and check-aci-edge-sandboxes, with Rust 1.95.0 and each crate's minimum version: the checks above, cargo check --target aarch64-apple-darwin of both crates, the artifact-bundling test, test-agent-bundling, check-guest-binaries, build-guest-binaries and test-guest-binaries, and the runtime adapter's protocol, host, and guest configurations, the last for x86_64-unknown-linux-musl. All pass.
  • validate-nvx's Linux steps under Python 3.12: compileall, the unittest suites, test_adversarial.py, test_hosts.py, and the --help checks. All pass.
  • check-quality's Python checks with the pinned ruff 0.15.22 and pyright 1.1.411: ruff check, ruff format --check, pyright --pythonplatform Linux and --pythonplatform Windows, scripts/check_markdown_links.py, and Specula's compileall and tests. All pass.

Linux x86_64 with the MSHV kernel, as root, because /dev/mshv is root-only on that host:

  • The same five test-microvm scenarios with --backend mshv: pass.
  • test-aci-edge-sandboxes --backend mshv: 14 passed, the new test included. The five tests that write through host mappings fail with Permission denied, as they do as root without this change.
  • The test adapter's VM scenarios, as on Windows, with NVX_HYPERVISOR=mshv and the guest agent built there: 14 passed.

Linux x86_64, KVM, as a non-root user, with these commits on an earlier head of #721, which pinned the previous OpenVMM and preceded #718, and the artifacts of #676's merge:

  • The Rust checks of check-nvx-rs and check-aci-edge-sandboxes but the bundling and guest-binary steps, check-quality's Python checks, and compileall, scripts/test_performance.py, and scripts/test_microvm_tests.py under Python 3.10. All pass.
  • The same test-microvm scenarios but sandbox-proxy-only, which did not exist yet, with --backend kvm: pass.
  • test-aci-edge-sandboxes --backend kvm: 19 passed.

On KVM and MSHV, OpenVMM ran with --cpu-profile host through a wrapper, because those hosts cannot present their CPUs' built-in profiles.

Left to CI: the backend matrix on the self-hosted runners (OpenVMM unit and VMM tests, the microVM scenarios, test-aci-edge-sandboxes, the test adapter's VM scenarios, and benchmarks), KVM on this head included, and check-quality's shell and PowerShell checks, whose inputs this PR doesn't change. CI also covers this head merged with the OpenVMM that dev pins since #788.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Cross-platform forced termination and runtime-record cleanup require human sign-off and confirmation of the remaining backend CI gates.

No open issues 🎉

What changed in this PR

This PR extends the shared NVX supervisor so owners can stop sandboxes started under a different runtime identity without contacting an incompatible guest protocol. It builds on #721’s store integration and implements the runtime-identity termination policy in #622.

Changes:

  • Adds identity-verified forced termination to the supervisor, instance API, CLI, and ACI backend while preserving ordinary same-identity stop behavior.
  • Separates boot-contract versioning from the microVM ABI and records boot contract version 3.
  • Expands termination, recovery, and identity-safety tests, with corresponding documentation and fixture updates.
File Description
scripts/​test_microvm_tests.py Extends lifecycle fakes and regression checks.
scripts/​nvx_tools/​sandbox_lifecycle_tests.py Tests cross-identity termination on real VMs.
nvx-rs/​tests/​supervisor.rs Covers termination bounds, permissions, and process identity.
nvx-rs/​tests/​store.rs Checks mismatches with boot contract 2.
nvx-rs/​tests/​fixtures/​store/​runtime.json Records boot contract 3.
nvx-rs/​tests/​fixtures/​store/​launch-spawned.json Updates spawned-launch boot contract.
nvx-rs/​tests/​fixtures/​store/​launch-prepared.json Updates prepared-launch boot contract.
nvx-rs/​tests/​fixtures/​store/​invalid/​runtime-unknown-field.json Updates the invalid runtime fixture’s contract.
nvx-rs/​tests/​fixtures/​store/​invalid/​launch-without-log.json Updates the invalid launch fixture’s contract.
nvx-rs/​tests/​fake_openvmm.rs Tests instance termination without control sessions.
nvx-rs/​tests/​cli.rs Tests mismatched stops, stale identities, and recovery.
nvx-rs/​src/​testing/​fake_openvmm.rs Records accepted control connections for tests.
nvx-rs/​src/​protocol/​root/​tests.rs Pins root tokens to the boot-contract version.
nvx-rs/​src/​protocol/​root.rs Documents boot-token versioning.
nvx-rs/​src/​host/​supervisor/​tests.rs Tests forced-only termination and error paths.
nvx-rs/​src/​host/​supervisor/​mod.rs Adds bounded termination and shared stop helpers.
nvx-rs/​src/​host/​store/​tests.rs Checks the dedicated boot-contract constant.
nvx-rs/​src/​host/​store/​identity.rs Uses boot-contract versioning in runtime identities.
nvx-rs/​src/​host/​instance.rs Exposes termination for mismatched instances.
nvx-rs/​src/​host/​cli/​sandbox/​lifecycle.rs Terminates mismatched VMs and emits warnings.
nvx-rs/​src/​contract.rs Defines boot contract 3 and versioning rules.
nvx-rs/​README.md Documents APIs and cross-identity lifecycle behavior.
doc/​usage.md Describes backend identity acceptance tests.
doc/​run.md Documents mismatched stops and lifecycle coverage.
aci_edge_sandboxes/​tests/​openvmm_fake.rs Checks forced stops after identity changes.
aci_edge_sandboxes/​tests/​openvmm_e2e.rs Tests reattachment and cross-identity termination.
aci_edge_sandboxes/​src/​openvmm/​mod.rs Routes mismatched stops through termination.
aci_edge_sandboxes/​src/​openvmm/​config.rs Documents the updated mismatch policy.
aci_edge_sandboxes/​README.md Explains termination behavior and test coverage.

Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Cross-platform forced termination needs a remaining contract correction and human review of exact-head backend validation.

1 open issue

Review effort: Balanced

Comment thread nvx-rs/src/host/instance.rs Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Identity-sensitive forced termination across Linux and Windows warrants final human review and confirmation of the pending backend CI matrix.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/a1-host-h4-instance-20261021 branch 2 times, most recently from 445bbe6 to a869a3e Compare October 22, 2026 23:51
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/a4-pr6-runtime-identity-20261021 branch from 3bed036 to 0ca7932 Compare October 23, 2026 00:22
@esaurez
Enrique Saurez (esaurez) requested a balanced review from Copilot October 23, 2026 00:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Identity-sensitive termination and runtime-record cleanup across Linux and Windows warrant human review of backend validation results for the current commit.

No open issues 🎉

Review effort: Balanced

Base automatically changed from esaurez/a1-host-h4-instance-20261021 to dev October 23, 2026 02:38
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/a4-pr6-runtime-identity-20261021 branch from 0ca7932 to 0240a37 Compare October 23, 2026 02:57
@esaurez
Enrique Saurez (esaurez) requested a balanced review from Copilot October 23, 2026 03:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Cross-platform forced termination and persistent-state cleanup warrant final human review and confirmation of backend CI on the current head.

No open issues 🎉

Review effort: Balanced

@esaurez
Enrique Saurez (esaurez) marked this pull request as ready for review October 23, 2026 03:35
Copilot AI balanced review requested due to automatic review settings October 23, 2026 04:02
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/a4-pr6-runtime-identity-20261021 branch from 0240a37 to 327267c Compare October 23, 2026 04:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Cross-platform forced termination and recovery require final human confirmation of process-safety and cleanup behavior, with exact-head backend validation still left to CI.

No open issues 🎉

Review effort: Balanced

@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/a4-pr6-runtime-identity-20261021 branch from 327267c to 6fd5543 Compare October 23, 2026 04:39
Copilot AI balanced review requested due to automatic review settings October 23, 2026 04:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Identity-sensitive process termination and runtime-record cleanup across Linux and Windows warrant human review alongside current-head backend CI results.

No open issues 🎉

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 23, 2026 05:38
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/a4-pr6-runtime-identity-20261021 branch from 6fd5543 to d18ea38 Compare October 23, 2026 05:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The CLI’s forced-termination wait can ignore Ctrl-C until the full timeout expires when OpenVMM cannot exit promptly.

1 open issue

Review effort: Balanced

Comment thread nvx-rs/src/host/cli/sandbox/lifecycle.rs Outdated
Copilot AI balanced review requested due to automatic review settings October 23, 2026 06:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Cross-platform forced termination and runtime-record cleanup require final human review with backend CI results for the current head.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

Enrique Saurez and others added 4 commits October 23, 2026 03:58
A VM that another runtime identity started may speak other boot or
control contracts, so nothing drives it through a control session. Its
owner could stop it only with the artifacts that started it. Now the
owner stops it by identity-verified forced termination:

- supervisor: Vmm::terminate takes the forced step alone, through the
  VMM's verified process identity, bounded by the caller. It clears the
  runtime record once the VMM has exited, and keeps it when the exit is
  not confirmed, so that a later call retries. Records whose process has
  exited, as after a reuse of its ID or an earlier boot, end nothing.
- instance: LockedInstance::terminate ends a Mismatched VM, and the
  Mismatched error points at it.
- CLI: stop terminates a VM of another runtime identity within --timeout
  and warns on standard error that it did. start and exec refuse it,
  naming the parts that differ, and point at stop. The stop of a VM of
  the same identity is unchanged.
- aci_edge_sandboxes: stop terminates such a VM and reports it as
  forced. Every other operation still fails with backend_error.
- fake OpenVMM: count the control connections that it accepts, so that
  tests prove that no session reached the VM.

Part of #622

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 84d06582-33fd-47b2-954e-904c4ea1a084
The scenario now restarts the CLI under an artifact root whose build
provenance names another initramfs, as after an upgrade, while the
sandbox runs. That CLI must refuse to exec and start, and deprovision
must find the sandbox running, while the CLI of the checkout still
drives the VM. Its stop must then terminate OpenVMM without a guest
shutdown, warn that it did, and leave no runtime record, capability, or
control endpoint, after which the sandbox starts again.

Part of #622

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 84d06582-33fd-47b2-954e-904c4ea1a084
The runtime identity recorded the microVM ABI version as its boot
contract, so the version stayed 2 when the boot tokens of a layered
root began to name the image's format and could keep the root's upper
in RAM (#676). A VM booted with the earlier tokens kept the runtime
identity of one booted with the new ones whenever the identities of its
artifacts were unchanged.

nvx::contract::BOOT_CONTRACT_VERSION now versions the boot contract:
the microVM ABI, the kernel command-line tokens that configure the
guest, and the root that the guest assembles from them. It is 3, for
the image format token and the RAM upper. The module documents when
each contract version must change, and a test pins the version beside
every token form of a layered root, whose exhaustive matches stop
compiling when a format or upper is added.

The store's fixtures and the CLI's goldens record boot contract 3.

Part of #622

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 84d06582-33fd-47b2-954e-904c4ea1a084
The CLI's stop of a VM of another runtime identity waited inside
Vmm::terminate for up to --timeout seconds, a wait that does not see Ctrl-C.
An OpenVMM that outlived its termination, as one in uninterruptible I/O,
therefore held the stop until the timeout passed.

The termination now waits one retry interval at most, and the stop's own
wait, which Ctrl-C ends, waits for the rest. Once OpenVMM has exited, a
second termination finds it exited, signals nothing, and clears its runtime
record, and the stop reports the forced termination as before. A stop that
Ctrl-C ends keeps the record, so that the next stop retries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 84d06582-33fd-47b2-954e-904c4ea1a084
Copilot AI balanced review requested due to automatic review settings October 23, 2026 11:12
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/a4-pr6-runtime-identity-20261021 branch from 143ba20 to b8fbce6 Compare October 23, 2026 11:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Cross-platform process termination and runtime-record recovery warrant final human review despite no identified blocking defects.

No open issues 🎉

Review effort: Balanced

@ppenna
Pedro Henrique Penna (ppenna) merged commit 2df68ba into dev Oct 23, 2026
44 checks passed
@ppenna
Pedro Henrique Penna (ppenna) deleted the esaurez/a4-pr6-runtime-identity-20261021 branch October 23, 2026 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants