Repository navigation
openvmm: carry a clone's activation record in the restore packet - #1419
Merged
Pedro Henrique Penna (ppenna) merged 3 commits intoNov 9, 2026
Merged
Conversation
Pin microsoft/openvmm#4996's head, d501e50660136000ec93412aea78fdafb9cf391c, "microvm: carry a clone's activation record in the restore packet", one commit on feature/microvm/dev 6cb6ba87c, and decode what it adds (#544). A platform restore with --restore-activation-record now ends the restore packet with the activation record of its clone, which the launcher supplies: the packet's flag bit 5, ACTIVATION, which was reserved, then, after the DNS server, the record's length, a little-endian u32 from 1 to 1024, and its bytes. The packet stays version 1, since every guest that decodes version 1 refuses a packet that sets a reserved bit, so a packet without the flag keeps its layout and meaning, and a guest that predates the flag fails a packet with a record rather than ignore it. No contract version, and no template's compatibility key, changes. The guest's decoder takes the flag: a header with it adds the record's length to the body, and the packet takes the record's bytes, bounded by nvx::activation::MAX_RECORD_BYTES, in four-byte reads that start on a four-byte boundary, before the time sample, in the same port transaction. A length of 0 or above the bound fails repair, as every invalid field does. Restore::activation_record returns the bytes, undecoded, and nvx-time's test mode prints them. An ignored test of tests/openvmm_vectors.rs compares the bound with OpenVMM's MAX_RESTORE_ACTIVATION_LEN. Tests: decoder tests of records of 1, 3, 602, and 1024 bytes, of lengths of 0, 1025, and u32::MAX, of a body that ends within the length, and of the flags that stay reserved; a test of the port reads of a packet whose record ends with a whole and a partial read, after which the time sample still decodes; and test-mode tests of the decoded record and of its refused lengths. Existing tests pass the decoder's new reader of the record, and build restores with the new field. The vector and CPU-profile tests pass against the pin. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The guide's restore packet (5.5.5) gains the ACTIVATION flag that the pinned OpenVMM defines: a platform restore that hands its clone an activation record with --restore-activation-record ends the packet with the record's length, a u32 from 1 to 1,024, and its bytes, which OpenVMM passes through without interpreting them, so the record carries no secret, and which cost the guest one port access per four bytes. It says why the packet stays version 1, gives the size that a record adds, and keeps ACK_REQUIRED on such a restore. Guest repair (5.5.7) validates the record's length, the compatibility model (1.5) names the record among the packet's contents, and the CI guide says that the ignored vector test also compares the record's bound with OpenVMM's (#544). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Maps NVX's new pin, d501e5066, the head of microsoft/openvmm#4996, at position 233, after position 232, 6cb6ba87c, the head of feature/microvm/dev, which microsoft/openvmm#4995 merged. The pin is 102 commits past the mapped tip, and positions 132-233 stay unassigned. Position 233 lets a platform restore end the restore packet with its clone's activation record: bit 5 of the packet's flags, which position 193 reserved when it restarted the packet at version 1, and --restore-activation-record, which the restore preparation of PR17's position 079 reads before any vCPU runs, only for the platform tier of PR19's position 093 and with the post-restore gate of PR20's position 095, and which the management RPC's conversion of position 228 takes too. It changes the restore packet of the time ABI series, which positions 132, 160, 181, and 193 add and change, and tests the portb device of PR15's position 060. Replaying it on upstream main conflicts in all nine of its files, since upstream main lacks the time ABI series, the microVM CLI and restore of PR15 and PR17, and the conversion of position 228, so it follows them upstream. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
4 tasks
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
Landing depends on unmerged OpenVMM #4996, verification of signed exact-pin provenance and exact-head CI, and maintainer confirmation of restore-ABI compatibility and reported platform validation.
No open issues 🎉
What changed in this PR
Adds guest-side support for carrying a platform clone’s activation record in the OpenVMM restore packet, advancing #544 while leaving Activate RPC replacement to a follow-up.
Changes:
- Decodes bounded activation records within the packet’s port transaction and exposes their bytes through
Restore::activation_record. - Adds decoder, port-read, guest-tool, and OpenVMM bound-consistency tests.
- Documents the packet extension, version-1 compatibility, and new OpenVMM pin’s roadmap position.
| File | Description |
|---|---|
| nvx-rs/tests/vectors/README.md | Documents the activation-bound consistency check. |
| nvx-rs/tests/openvmm_vectors.rs | Checks that NVX and OpenVMM share the activation-record bound. |
| nvx-rs/tests/guest_tools.rs | Tests activation-record output and invalid lengths. |
| nvx-rs/src/guest/time/test_mode.rs | Reads and prints activation bytes in test mode. |
| nvx-rs/src/guest/time/decoders/tests.rs | Tests record decoding, bounds, and reserved flags. |
| nvx-rs/src/guest/time/decoders.rs | Adds the activation flag, length validation, and record decoding. |
| nvx-rs/src/guest/time/api/tests.rs | Tests the accessor and record reads before time sampling. |
| nvx-rs/src/guest/time/api.rs | Reads activation bytes and exposes them on Restore. |
| nvx-rs/src/contract.rs | Documents the extension’s version-1 compatibility. |
| nvx-rs/README.md | Describes the expanded OpenVMM consistency checks. |
| nvx-rs/public-api/linux.txt | Records the new public accessor. |
| doc/openvmm-upstream-roadmap.md | Maps the new pin at position 233 and its dependencies. |
| doc/design/5.5-restore-protocol.md | Specifies activation layout, bounds, gating, and compatibility. |
| doc/design/1.5-compatibility-and-versioning-model.md | Includes activation records in the restore-packet contract description. |
| doc/ci.md | Documents CI verification of the shared activation bound. |
Review effort: Balanced
Pedro Henrique Penna (ppenna)
deleted the
agents/issue-544-activation-packet
branch
November 9, 2026 17:43
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #544
What and why
#544 asks to deliver a platform clone's activation record before the first
restored vCPU runs, in the restore packet, or in a configuration region outside
captured RAM only if measurements call for one, and then to retire the
activation RPC: the Activate in which
nvx::host::template::clonesends aclone its record on its control session, once the host has connected after the
restore. Its scope change asks to measure the byte channel first, against a
budget for the slowest backend, and to keep the packet if the expected record
fits. This pull request is the first part: the restore packet's record in
OpenVMM, its pin, the guest's decoding of it, and the measurements, which keep
the packet. The libraries still deliver the record in Activate; part 2, below,
moves them to the packet and retires Activate.
microsoft/openvmm#4996, one commit on
6cb6ba87c, the head offeature/microvm/dev:ACTIVATION, which was reserved, saysthat the packet ends, after the DNS server, with the record's length, a
little-endian
u32from 1 to 1,024, then the record's bytes, which OpenVMMpasses to the guest without interpreting them.
--restore-activation-record PATH, and the management RPC'sMicrovmSnapshotConfig.restore_activation_record_path(15), which theserver converts to that option as OpenVMM: support sandbox blocks, networking, and supplied RAM in the microVM management RPC #760's conversion does every restore
field, read the record before any vCPU runs, reading at most one byte past
the bound. Only a platform snapshot admits it, and such a restore gates host
input until the guest acknowledges it, so the guest has read the record
before host input reaches it.
In NVX, the time ABI's decoder (
nvx::guest::time) takes the flag: the headeradds the record's length to the body, and the packet takes the record's bytes,
bounded by
nvx::activation::MAX_RECORD_BYTES, in the packet's porttransaction, before the time sample; a length of 0 or above the bound fails
the repair, as every invalid field does.
Restore::activation_recordreturnsthe bytes, undecoded, which part 2's activation decodes, and
nvx-time's testmode prints them. An ignored test of
tests/openvmm_vectors.rschecks thatOpenVMM's bound is
MAX_RECORD_BYTES. The guide's restore packet (5.5.5)describes the flag, the record's layout and cost, and why the packet keeps its
version.
Decisions:
packet that sets a reserved flag bit, so a guest that predates the flag fails
a restore that hands it a record rather than ignore the record, and a packet
without the flag keeps its layout and meaning (both checked on KVM, below).
No contract version changes, so existing snapshots and templates stay
restorable, template keys,
SOURCE-MANIFEST.json, and the release thatbundledpins stay valid, and the crate's publication isn't blocked.nvx-rs: replace activation RPC delivery with an uncaptured VMM configuration region #544's scope change proposed a new version; the network settings' precedent
bumped the version only for the packets that carried the new field, which
the reserved bit makes unnecessary here.
u32length. It keeps the record on a four-byte boundary of the packet(24-byte header, 16-byte ranges, 64 bytes of entropy, 16-byte DNS server,
4-byte length): the guest reads the packet four bytes per port access and
keeps only the bytes that it asked for of the last one, so a read that
spanned a 2-byte length and the record would drop the record's first bytes.
MAX_RECORD_BYTES, whose largest record holds602 bytes. OpenVMM's bound is the same, and the vector test keeps them equal.
a tenant's workload with the settings that it was captured with, and one
without a tier has no activation.
Measurements
Each four bytes of the record cost the guest one port access. Measured on the
three test hosts (Azure VMs: nested virtualization, shared with other jobs, so
their p95 is noisy, KVM's above all), with the platform snapshot of
nvx.py benchmark --suite platform-snapshot-restore-vcpu(capacity 8,boot-online 1, sandbox blocks), restored at target 1, a gated restore whose
guest reads the packet through
nvx-time, with records of 0, 64, 256, and1,024 bytes, interleaved, forward and backward in turn, 2 warmups and 30 runs
per size and backend. A measurement-only span in OpenVMM's portb device, not
in this pull request, timed the packet's read from its selection to its last
byte. 4,096 bytes exceeds the bound, so its row is extrapolated from the cost
per read.
Packet read, p50 / p95 (ms):
The cost of one four-byte read, by least squares over the per-size medians:
KVM 9.7 µs, MSHV 27.4 µs, WHP 53.5 µs. What a record adds, at that cost:
The restore to the guest's readiness marker, p50 / p95, from no record to
1,024 bytes: MSHV 50.03 / 55.16 to 57.81 / 61.95 ms, WHP 218.30 / 235.12 to
231.53 / 267.25 ms; on KVM the host's noise exceeds the difference
(114.71 / 152.41 to 117.29 / 146.29 ms).
Budget and delivery. #544 leaves the budget to agree; this pull request
proposes 10 ms at p50 on the slowest backend for the largest record that
nvx::activationproduces, and 5 ms for the expected records, 131 to 265bytes. WHP, the slowest, takes 8.1 ms and 1.8 to 3.6 ms, and MSHV and KVM
less, so the packet fits on every backend, and no configuration region, nor
its change of the machine contract, is needed. Records near the 4 KiB that the
issue lists would not fit WHP's budget, but none needs more than 602 bytes.
Part 2
Left for #544's next pull request:
launch hands OpenVMM the record that it writes to the sandbox, with
--restore-activation-record.record, and
nvx::guest::clone::Activationvalidates and applies it rightafter the repair, before the host connects, and sends Ready unprompted once
the session's reset came, in that session and in each later one.
nvx::guest::clone, fromnvx::host::template::clone(CloneControl::activatebecomes a wait forReady, and reattach checks a later session's Ready), and from the runtime
adapter's framing and agents.
clones would otherwise fail their repair after their launch.
Activate path, on KVM, MSHV, and WHP.
Commits
openvmm: pin the restore packet's activation recordmoves the gitlink from6cb6ba87ctod501e50660136000ec93412aea78fdafb9cf391c, the head of[DO NOT REVIEW] microvm: carry a clone's activation record in the restore packet openvmm#4996, and changes the guest's decoder, its tests, the
public API snapshot, and the vector test with it. The protocol vectors and
CPU profiles that
nvx-rscopies are unchanged.doc: describe the restore packet's activation recordupdates the guide'srestore packet and guest repair (5.5.5, 5.5.7), the compatibility model
(1.5), and the CI guide's account of the vector test.
doc: map the restore packet's activation record in the OpenVMM roadmapmaps position 233, unassigned, 102 commits past the mapped tip, with the
mapped work that it extends: the restore preparation of PR17's position 079,
the platform tier of PR19's position 093, the gate of PR20's position 095,
and the management RPC's conversion of position 228. It conflicts with
upstream main in all nine of its files, so it follows the time ABI series,
PR15, PR17, PR19, PR20, and PR25 upstream.
Validation
Local gates,
local_gates.py --base origin/dev --run --keep-goingat thishead:
test-changes,markdown-links,test-layout,openvmm-vectors,openvmm-cpu-profiles, and thenvx-rsgates (format,lint, test, bundling, doc, MSRV, and the runtime adapter's protocol and host)
passed.
nvx-cliran 2,188 tests, 88 of them skipped, and failed only in thethree unreached cases of
test_host_loopback_allow_script_checks_publication_and_traffic,which time out in Git for Windows'
shon this machine and pass in CI andon Linux, and
test_a_shallow_clone_cannot_count_the_chain, which passesalone; CI's
NVX CLI / WindowsandNVX CLI / Linuxpassed.nvx-mshv-test, Azure Linux 3.0):test-changes,markdown-links,test-layout,openvmm-vectors(withthe_activation_bound_is_the_submodules),openvmm-cpu-profiles, and everynvx-rsgate, the guest's binaries and the runtime adapter's protocol, host,and guest included, passed.
nvx-clifailed only because the host's Pythonhas no
pip, andopenvmm-ciwaits for [DO NOT REVIEW] microvm: carry a clone's activation record in the restore packet openvmm#4996's CI, whoseunit tests and Linux VMM tests have passed.
OpenVMM, at
d501e5066:cargo clippy --all-targets,cargo doc --no-deps,and
cargo nextest run --profile agentofchipset_resources,chipset,openvmm_entry, andopenvmm_helperspassed on Windows (522 tests) and onLinux (554 tests), and
cargo xtask fmt --fixis clean.Remote runs on the test hosts, with OpenVMM built from the pin and the guest
rebuilt (the guest's artifacts of this head are byte-identical to those of
the first round). The restore scenarios are
snapshot-core,snapshot-tiers,restore-processors,restore-memory,untiered-network-policy,platform-restore-processors,platform-network-policy, andtime-abi-conformance, ofnvx.py test-microvm.dev2a061ea61ona386c22e46a7eec4be, pind501e5066nvx-kvm-testkvmvirtual-machinev0.1.0-dev.8ebadbd3dae0(OpenVMM98e5b3ca5): OpenVMM refuses--restore-activation-record(unexpected argument, exit 2). Its guest, with this pin's OpenVMM and a platform snapshot: a restore without a record reaches its marker; one with a 64-byte record fails closed,G_REPAIR_PACKET,restore packet flags 0x24 set reserved bitsnvx-mshv-testmshvvirtual-machinenvx-whp-testwhpvirtual-machineLeft to CI: the full matrix (the runtime adapter's VM suites, the Ubuntu
guest, every microVM scenario, the guest's determinism, and the benchmarks),
nvx-clion Linux, and OpenVMM's CI of microsoft/openvmm#4996, which theopenvmm-cigate checks. CI's run of this head,https://github.com/microsoft/nvx/actions/runs/42657899579, passed, its
OpenVMM CI of the pinned revisionjob included. OpenVMM's run of the pin,https://github.com/microsoft/openvmm/actions/runs/42650574781, passed every
job but the non-required
run vmm-tests [x64-windows-amd-snp], in which twoHyper-V OpenHCL TPM tests timed out (
tpm138::..._snp_ak_cacheandtpm::..._snp_ak_pub_refresh), as other TPM tests of that job did onmicrosoft/openvmm#4993 and microsoft/openvmm#4994; the change touches neither
OpenHCL nor the TPM.
Changes to existing tests
tests-modified:check_test_changes.py --base origin/devlists 8 changesto existing tests, all mechanical, and 11 added tests:
guest::time::decoders::tests:packets_decode_their_header_then_their_body,packet_errors_name_the_invalid_field,network_packets_carry_their_network_settings, andpackets_refuse_malformed_network_settingspassRestorePacket::parseitsnew reader of the record, which reads nothing for packets without one.
guest::time::api::tests: theRestore::tohelper,requests_and_restores_carry_the_abi_values, anda_restore_hands_over_its_seed_once_and_formats_none_of_itbuild aRestorewith the newactivationfield,None, andrequests_and_restores_carry_the_abi_valuesalso checksRestore::activation_record;restore_inputbecomes a wrapper ofrestore_input_with, which can append a record, and returns the same input.OpenVMM pin
d501e50660136000ec93412aea78fdafb9cf391c, one commit on6cb6ba87c, thehead of
feature/microvm/dev, which NVX pinned before; no otherprerequisite.
d501e5066is patch-identical (git patch-id --stable) to2a061ea61, the same change with its fixups oned8fc0713, which themeasurements and the first round above used.
6cb6ba87c); positions 132-233stay unassigned.
Landing order
Land microsoft/openvmm#4996 first, by fast-forwarding
feature/microvm/devtod501e50660136000ec93412aea78fdafb9cf391c(OpenVMM's merge button squashes,which would leave the pin off the branch); then merge this pull request.