Skip to content

[DO NOT REVIEW] microvm: carry a clone's activation record in the restore packet - #4996

Open
Pedro Henrique Penna (ppenna) wants to merge 1 commit into
feature/microvm/devfrom
copilot/nvx-issue-544-activation-packet
Open

Pedro Henrique Penna (ppenna) wants to merge 1 commit into
feature/microvm/devfrom
copilot/nvx-issue-544-activation-packet

Conversation

@ppenna

@ppenna Pedro Henrique Penna (ppenna) commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Lets a platform restore hand its clone an activation record in the restore packet, for microsoft/nvx#544. A platform clone takes the settings of its own launch, its activation record, from its host after the restore: NVX's guest waits, once it has acknowledged the restore, for the record in an Activate on its control session, which the host sends only once it has connected. The record is small, bounded, and carries no secret, so the restore packet, which the guest reads before it acknowledges the restore, can carry it, sealed before the first restored vCPU runs.

One commit on 6cb6ba87c, the head of feature/microvm/dev. microsoft/nvx#1419, part of microsoft/nvx#544, pins this head.

Carry a clone's activation record in the restore packet (d501e5066)

  • Change. Bit 5 of the packet's flags, FLAG_ACTIVATION, which was reserved, says that the packet ends, after the DNS server record, with the record's length, a little-endian u32 from 1 to 1024 (MAX_RESTORE_ACTIVATION_LEN), then the record's bytes, which OpenVMM passes to the guest without interpreting them. RestorePacketBase::activation carries them; encode and decode lay them out and bound them, with RestorePacketError::Activation for an empty or oversized record.
  • Options. --restore-activation-record PATH, and the management RPC's MicrovmSnapshotConfig.restore_activation_record_path (15), which the server converts to it as it converts the other restore fields, read the record in prepare_restore, before any vCPU runs, reading at most one byte past the bound. Only a platform snapshot admits it: a snapshot of a later tier continues a tenant's workload with the settings that it was captured with, and one without a tier has no activation. Such a restore gates host input until the guest acknowledges it, so the guest reads the record before host input reaches it.
  • Layout. The u32 length 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), since the guest reads the packet four bytes per port access, and a read split across the length and the record would drop bytes.
  • Alternatives. A new packet version would refuse every snapshot whose guest decodes only version 1, even at restores that carry no record. The packet stays version 1 instead: every guest that decodes version 1 refuses a 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. A configuration region outside captured RAM would also deliver the record before the first vCPU runs, but would change the machine contract; nvx-rs: replace activation RPC delivery with an uncaptured VMM configuration region nvx#544 measures the packet's cost per four-byte read on KVM, MSHV, and WHP, and keeps the packet.
  • Compatibility. Every restore without the option produces the same packet as before, and the snapshot format is unchanged.
  • Tests. golden_packet_with_an_activation_record and activation_records_are_bounded encode and decode records of 3 and 1024 bytes, and refuse lengths of 0, 1025, and u32::MAX, packets that end within the length or before or after the record, and the flag's absence; decode_rejects_malformed_packets now refuses bits 6 and 7, since bit 5 is defined. a_restore_packet_carries_an_activation_record serves a 1021-byte record through portb whose last four-byte read takes one byte. The command line's and the RPC's tests cover the option's requirements and its conversion, only_a_platform_restore_hands_over_an_activation_record the tier check, activation_records_hold_one_to_the_most_bytes the file's bounds, and time_abi_restore_packet_carries_the_controller_fields the packet's field.
  • Guide. The user guide's restore packet describes flag bit 5 and the record's layout, the CLI reference the option, and the gRPC reference the field.

Validation: cargo clippy --all-targets, cargo doc --no-deps, and cargo nextest run --profile agent of chipset_resources, chipset, openvmm_entry, and openvmm_helpers pass on Windows (522 tests) and on Linux (Azure Linux 3.0, 554 tests), and cargo xtask fmt --fix is clean. NVX's restore scenarios passed on KVM, MSHV, and WHP with this commit and with 2a061ea61, its patch-identical predecessor on ed8fc0713, which NVX's measurements of the record's cost used. On KVM, a platform restore that hands a 64-byte record to a guest that predates the flag fails closed (G_REPAIR_PACKET, restore packet flags 0x24 set reserved bits), and one without a record reaches its marker.

A platform clone takes the settings of its own launch, its activation
record, from its host after the restore: NVX's guest waits, once it has
acknowledged the restore, for the record in an Activate on its control
session, which the host sends once it has connected. The record is
small, bounded, and carries no secret, so the restore packet, which the
guest reads before it acknowledges the restore, can carry it, and the
clone can then activate before the host connects, without a request of
the host's (microsoft/nvx#544).

The packet's flag bit 5, ACTIVATION, which was reserved, now says that
the packet ends, after the DNS server, with the record's length, a
little-endian u32 from 1 to 1024, which keeps the record aligned to the
guest's four-byte reads, and the record's bytes, which OpenVMM passes
to the guest without interpreting them. The packet stays version 1:
every guest that decodes version 1 refuses a packet that sets a
reserved bit, rather than ignore its record, and a packet without the
flag keeps version 1's layout and meaning, so snapshots whose guests
predate the flag stay restorable.

--restore-activation-record PATH, and the management RPC's
MicrovmSnapshotConfig.restore_activation_record_path (15), which takes
the option, read the record of a restore before any vCPU runs, and at
most one byte past the bound. Only a platform snapshot admits it: a
snapshot of a later tier continues a tenant's workload with the
settings that it was captured with, and one without a tier has no
activation. Such a restore gates host input until the guest
acknowledges it, so the guest has read the record before host input
reaches it.

Tests: the packet's encoding with a record, its bounds, and malformed
lengths; the portb device serving a record whose last read is partial;
the option's rules on the command line and in the RPC; the tier check;
the file's bounds; and the packet's fields.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna) added a commit to microsoft/nvx that referenced this pull request Nov 9, 2026
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>
Pedro Henrique Penna (ppenna) added a commit to microsoft/nvx that referenced this pull request Nov 9, 2026
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>
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.

1 participant