Skip to content
This repository was archived by the owner on Oct 22, 2026. It is now read-only.

microvm: add bind-once read-only image slots - #116

Closed
esaurez wants to merge 304 commits into
mainfrom
esaurez/microvm-image-slots
Closed

esaurez wants to merge 304 commits into
mainfrom
esaurez/microvm-image-slots

Conversation

@esaurez

@esaurez esaurez commented Oct 6, 2026 •

Copy link
Copy Markdown

Summary

Adds bind-once read-only virtio-blk image slots to the microVM profile. Image slots extend microVM ABI 2 in place, without a separate ABI or backwards-compatibility path because the feature is not deployed. A VM without slots keeps its original device topology, discovery tokens, and snapshot shape.

Base: main at f4566bab, which includes #124 and #125. microsoft/nvx dev pins that revision, and microsoft/nvx#406 pins this PR's head.

A VM that declares slots also uses microVM ABI 2, with four permanent slot identities:

Slot MMIO IRQ (ISA, edge) Shared-status offset
image0 0xd0008000 1 0x20
image1 0xd0009000 13 0x24
image2 0xd000a000 14 0x28
image3 0xd000b000 15 0x2c

image0 reuses the MMIO window and shared-status word of the second virtio-fs share, and image1 reuses its IRQ 13. A VM with image slots therefore attaches at most one virtio-fs share. The CLI, VM configuration, machine validation, and the snapshot contract reject the combination. The discovery token is microvm_image_slots=4.

Activation (no hotplug)

  • A cold boot activates B slots (--microvm-image-slot-boot-count, 1..4, default 1). Inactive slots are virtio-mmio placeholders (device ID 0).
  • A restore may activate a longer prefix with --restore-image-slots N (B <= N <= 4). The target travels in restore packet version 4: flag bit 4 is set and header byte 7, previously reserved zero, holds N. No portb status bit advertises it. An explicit target always holds the restore gate until the guest verifies the active prefix and acknowledges, through both the CLI and the management RPC. No slot is activated after readiness, and no device is added or removed at runtime.
  • Active slots start as empty read-only virtio-blk devices (512-byte logical and 4096-byte physical blocks). I/O to an empty slot fails with IOERR.

Binding

  • bind_image_slot binds a validated read-only regular file once. The VMM opens and retains the handle, sets the capacity, and raises a virtio configuration change. Repeating the same identity is idempotent. A different identity on a bound slot, an inactive slot, invalid media, mismatched geometry, and writes are rejected. Identities are 1 to 1024 bytes. There is no unbind, eject, or rebind; media is released when the VM exits.
  • query_image_slots reports each slot as inactive, empty, or bound with its identity, and is the authority after a host reconnect.

Host control

Adds a versioned (v1) host-control protocol over an owner-restricted Unix socket or Windows named pipe. It checks the local peer UID/SID and authenticates with the existing 32-byte control capability. CLI launches read the capability once from stdin; management-RPC launches carry it in the authenticated request, and the management RPC redacts it from its request log. Frames in both directions are limited to 64 KiB: a response that would exceed the limit becomes a response_too_large error. A connection closes after 60 seconds without a request or inside an incomplete frame. A client ends its session by closing the connection after a response: end-of-stream at a frame boundary is a normal close, while a close inside a frame is an error.

Snapshots

Slot-enabled ABI-2 snapshots record the capacity and the boot-active count (contract fields 34 and 35), the four stable devices, and empty media state. Without slots, ABI-2 snapshots record zero capacity and boot-active count and have no slot devices. Capture is rejected before the guest is quiesced while any slot is bound; binding and capture share an exclusion gate.

Machine validation

Machine validation admits exactly the four image-slot transports when slots are declared, and raises the device limit by the slot capacity only in that case. Machines without slots keep the original inventory and limit, and standard machines reject an image-slot configuration.

Commits

  • f5c8dab0 virtio_blk: add bind-once image slots
  • 0a3dc07e microvm: add bind-once read-only image slots
  • 4eb09487 microvm: fold image slots into ABI 2
  • 67b641d8 microvm: harden host-control framing, logging, and cleanup

Rebase onto main

The previous head 41a595e9 was based on main 96e2363b. The three commits were first replayed onto #124 so that microsoft/nvx#406 could pin one revision containing both. After #124 and #125 merged, they were replayed onto main f4566bab. Each commit builds, and range-diff shows the last move changed no patch.

67b641d8 addresses the review findings on 41a595e9:

  • The management RPC logs a CreateVM request without its host-control capability.
  • The idle timeout also bounds a request frame's payload, so a client that stalls inside a frame cannot hold the single-connection endpoint.
  • Responses stay within the 64 KiB frame limit.
  • A management-RPC image_slot_boot_count without image_slots is rejected, as on the command line.
  • After a committed snapshot, a failed host-control socket removal is reported like the console sockets; the unreachable duplicate removal is gone.

Validation

Current head 67b641d8:

  • Windows, Rust 1.95: cargo nextest run --profile agent -p virtio_blk -p virtio -p virtio_resources -p chipset_resources -p chipset -p openvmm_defs -p openvmm_helpers -p openvmm_entry -p openvmm_ttrpc_vmservice -p net_backend_resources passed 583 tests. cargo clippy --all-targets -- -D warnings, cargo fmt --check, and cargo doc --no-deps with RUSTDOCFLAGS=-D warnings are clean for the changed packages. cargo check --tests passes at each commit.
  • Linux, Docker, Rust 1.95.0: cargo test for the same ten packages passed 621 tests; cargo clippy --all-targets -- -D warnings and cargo fmt --check are clean.
  • New tests cover capability redaction, the stalled-payload timeout, the response limit, and the boot-count validation.
  • Live Linux/MSHV through Adopt bind-once microVM image slots microsoft/nvx#406 (3b885041 and 874fd540, which pin this head). Host: AMD EPYC 9V74, Linux 6.6.148-200.mshv-b1f02da.azl3; static musl OpenVMM from nvx.py build-openvmm --backend mshv with Rust 1.95.0 (SHA-256 b2c02064…). This host's hypervisor cannot present the built-in amd.genoa.v1 profile that --cpu-profile auto selects, so a test-only wrapper passed --cpu-profile host to cold microVM boots; restores and the CPU-profile fallback scenario ran unchanged.
    • The NVX image-slot scenario passed on the Alpine and Azure Linux guests. It binds slot 0 with B = 1, rebinds it idempotently, rejects a different identity, and reads it; then it captures an empty-slot snapshot, restores it with four active slots, and binds and reads slots 1, 2, and 3. Every slot IRQ (1, 13, 14, 15) therefore delivers a configuration change.
    • The NVX managed lifecycle and the default microVM suite (39 runs, 362 s, including net_backend_resources: select egress rules by protocol without a port #124's and net_backend_resources: select egress rules by destination-port range #125's egress acceptance) passed.
    • Cold boot p50 (n = 21 each): 234.4 ms without slots, 237.6 ms with one active slot (+3.3 ms), and 240.5 ms with four (+6.1 ms).

Earlier heads (before the ABI-2 consolidation): Linux/MSHV runs of the NVX image-slot scenario, the default microVM suite, and the image-slot boot benchmark, and microsoft/nvx#406 CI on KVM, MSHV, and WHP, all passed.

Follow-up

Track additional microVM IRQ lines and possible expanded IOAPIC routing in microsoft#123. More IRQ lines alone do not resolve the separate second-filesystem MMIO overlap.

ppenna and others added 30 commits September 28, 2026 22:41
Restoring an x86 snapshot is only safe on a partition that presents the
guest with the CPU that it was saved on: the same vendor and CPUID results,
XSAVE layout, and processor features. There is no canonical representation
of that effective CPU that a snapshot could record and a restore could
compare.

Add `CpuCompatibilityContract`, a protobuf description of the effective
CPU: the vendor, every CPUID leaf with its mask, the supported XCR0 and XSS
features, the standard and compacted XSAVE sizes and component layout, the
x2APIC, CET, SGX, and TSC_AUX features, and the physical address width.
`CpuCompatibilityContract::new` builds it from the partition capabilities
and the effective CPUID leaves.

Add `Partition::cpu_compatibility_contract` for x86 guests, and implement
it in the KVM, MSHV, WHP, and OpenHCL partitions from their capabilities
and CPUID leaf sets.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
The saved state of a VP did not include IA32_TSC_DEADLINE, so a guest that
arms its local APIC timer in TSC-deadline mode loses the pending deadline
across a save and restore. Nothing could move a saved APIC timer forward by
the time that the VM spent stopped either.

Add a `TscDeadline` VP state element, which is present when the partition
supports TSC-deadline mode, and record that support, as reported by CPUID,
in the partition capabilities and the CPU compatibility contract. The
deadline is not compared after restore, since it can expire between
restoring it and reading it back. KVM, WHP, and the hypervisor-backed
OpenHCL VPs access the IA32_TSC_DEADLINE register; the OpenHCL SNP and TDX
VPs do not implement it. MSHV keeps reporting no TSC-deadline support,
since it does not reliably deliver TSC-deadline events to direct-boot
guests.

Add `Apic::advance_timer`, which advances a one-shot or periodic LAPIC
timer by a downtime at a given timer frequency and queues the timer
interrupt if the timer expired, and `Apic::advance_tsc_deadline`, which
queues the interrupt of an armed TSC deadline that an advanced TSC passed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
Restoring a snapshot, possibly on another host, requires the guest TSC to
keep its saved frequency and to account for the time that the VM spent
stopped. Partitions cannot report or request a TSC frequency, though, and
processors cannot move their TSC forward.

Add defaulted `Partition` hooks that return the effective guest TSC and
local APIC timer frequencies, request a TSC frequency, and advance any
backend-specific guest clock by a snapshot downtime, and a defaulted
`Processor::advance_tsc`, which advances the stopped VTL0 TSC by a number
of guest cycles through the VP state access. The defaults report no
frequency and ignore frequency requests, so that backends opt in
individually.

Add `tsc_frequency_cpuid_leaves`, which builds a CPUID leaf 0x15 that
exposes an exact TSC frequency, including frequencies that do not fit the
32-bit crystal frequency, without overflowing the frequency computation of
the Linux kernel. Detect KVM's paravirtual clock from the hypervisor CPUID
leaves, and record it in the partition capabilities and the CPU
compatibility contract.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
KVM partitions did not report their TSC frequency, so guests had to
calibrate it, and a restored VM could not be given the TSC frequency that
it was saved with. The kvm-clock MSRs of a guest that uses KVM's
paravirtual clock were not saved either, so the guest lost its clock
configuration across a save and restore.

Expose the exact TSC frequency of the BSP in CPUID leaf 0x15, and implement
the partition clock hooks: report the TSC frequency and the 1 GHz frequency
of the in-kernel local APIC timer, set the TSC frequency of every vCPU, and
advance the kvm clock by a snapshot downtime. Advance the TSC of a vCPU by
adjusting its TSC offset, since KVM can discard a sub-second write to
IA32_TSC as a synchronization attempt.

Save and restore the kvm-clock wall-clock and system-time MSRs with the
synthetic MSRs when the partition exposes the kvm clock, and make the
synthetic MSR and reference time state present for such partitions even
without Hyper-V enlightenments.

Add the KVM ioctls that get and set the TSC frequency of a vCPU and its
TSC offset device attribute.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
…frequency

MSHV partitions did not report their TSC frequency, so guests had to
calibrate it, and nothing verified that a snapshot is restored on a host
with the TSC frequency that it was saved with. The TSC of each VP is also
restored at a different host time, so the counters of a restored SMP guest
drift apart.

Expose the exact TSC frequency of the partition in CPUID leaf 0x15, and
implement the partition clock hooks: report the TSC and local APIC timer
frequencies, reject a requested TSC frequency that differs from that of
the partition, since MSHV cannot change it, and, after a snapshot downtime,
freeze partition time and align the counters of all VPs to the advanced
BSP counter.

Hide the TSC-deadline timer from the guest, in both the processor features
of the partition and CPUID, since MSHV does not reliably deliver its events
to direct-boot guests, and report that ordinary state access does not
freeze the partition clock.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
…erent

WHP partitions did not report their TSC frequency, so guests had to
calibrate it, and nothing verified that a snapshot is restored on a host
with the TSC frequency that it was saved with. The TSC of each VP is also
restored at a different host time, so the counters of a restored SMP guest
disagree with each other.

Expose the exact TSC frequency of the partition in CPUID leaf 0x15, and
implement the partition clock hooks: report the TSC frequency and the local
APIC timer frequency, reject a requested TSC frequency that differs from
that of the partition, and, after a snapshot downtime, align the counters
of all VPs of an SMP partition with partition time suspended.

To keep TSC reads coherent across the VPs, a restored SMP partition that
does not use nested virtualization then derives the TSC of each VP from the
partition-wide reference time. While this restored-TSC clock is active, WHP
exits on RDTSC and on accesses to IA32_TSC, TSC_ADJUST, and
IA32_TSC_DEADLINE, and the VP state accessors read and restore the TSC
through the clock. A VP whose guest writes its TSC or changes its
TSC_ADJUST leaves the clock and gets the native WHP semantics again, and
resetting the partition disables the clock.

Read IA32_TSC_DEADLINE before the APIC state when saving a VP, so that the
deadline cannot expire between the two reads. Add the WHP properties and
registers that this needs, and report a failure to inject a general
protection fault from an MSR exit instead of panicking.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
When a VM is restored after it was stopped for some time, the TSC of each
VP and its local APIC timer continue from their saved values, so the guest
does not observe the elapsed time and its timers fire late.

Add `PartitionUnit::advance_tsc`, which advances the TSC of every stopped
VP by the downtime at the guest TSC frequency. It also advances the local
APIC timer of each VP at the APIC timer frequency, when the backend reports
one, and fires an armed TSC deadline that the advanced TSC passed. Each VP
adjusts its TSC through `Processor::advance_tsc`, reprograms its APIC and
TSC deadline, and then verifies that its counter advanced by at least the
requested amount, since a backend could discard the adjustment.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
Measuring where snapshot capture and restore spend their time requires
timing individual lifecycle phases in several OpenVMM components and
processes, and correlating the results, without changing the behavior or
the output of ordinary runs.

Add a `profile` module to `openvmm_defs`. When the
`OPENVMM_STARTUP_PROFILE` environment variable is set to a nonempty value
other than `0`, a `ProfileSpan` measures an exclusive phase or a cumulative
milestone and writes one machine-readable
`OPENVMM_SNAPSHOT_PROFILE_V1` record to stderr, with its duration, the time
since process start, the process ID, and optional byte and fault counters.
Otherwise spans are inert. OpenVMM initializes the process-relative clock
at startup; the lifecycle phases add their spans as they are implemented.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
…ecoding

The snapshot manifest identified its format only by a version number:
nothing marked the file as an OpenVMM snapshot, or recorded which
saved-state schema `state.bin` holds and how long it is. Reading a snapshot
also decoded `manifest.bin` and read `state.bin` without any size limit.

Introduce manifest version 3, which adds a format magic, the saved-state
schema version and protobuf root type, and the length of `state.bin`, and
move the format definitions and their validation into a `format` module.
`SnapshotManifest` implements `Default` with the current format
identifiers, and the callers that build manifests take the fields that
they do not set from it. Manifests of the earlier version 2 format, which
records SHA-256 digests of `state.bin` and `memory.bin` instead, are
accepted as well; version 1 manifests are no longer accepted.

Writing a snapshot records the length of `state.bin` and rejects saved
state larger than 256 MiB. Reading a snapshot limits `manifest.bin` to
1 MiB, validates the manifest format before it reads `state.bin`, and
requires `state.bin` to have the recorded length.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
Saving a snapshot created the destination directory, wrote the manifest and
the saved state into it, and hard-linked the memory backing file as
`memory.bin`. A failure could leave a partial snapshot at the destination,
nothing was flushed to stable storage, an existing directory was silently
reused, and the hard link made the snapshot an alias of live guest RAM that
any later write to the backing file corrupted. The backing file also had to
be on the same filesystem as the snapshot.

Publish snapshots through a private, uniquely named staging directory next
to the destination. `state.bin`, `memory.bin`, and `manifest.bin` are written
and flushed there, the staging directory is flushed, and it is then renamed
to the destination without replacing an existing path. The rename is the
commit point, after which the parent directory is flushed. A failure before
the commit removes the staging directory and leaves the destination absent;
`SnapshotWriteError` distinguishes it from a committed snapshot whose parent
directory could not be flushed.

`memory.bin` is now an independent copy of guest RAM, made from an open
handle so that replacing the path of the backing file cannot substitute
other bytes, and `write_snapshot_from_memory_file` accepts that handle
directly. The copy clones the file when the filesystem supports it, and
otherwise copies only allocated ranges or nonzero data on Linux and copies
densely on Windows. The backing file must have the length that the manifest
records.

Add the snapshot file-system helpers that this needs: private directories,
no-replace renames, directory flushes, regular-file access that rejects
Windows reparse points, and sparse-aware copies. Update the Guide.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
Copying guest RAM into `memory.bin` keeps a snapshot independent of the
source VM, but it doubles the storage and the capture time for a VM whose
backing file OpenVMM itself creates and owns, and whose source is terminated
after capture anyway.

Add `write_snapshot_from_owned_memory_file`, which publishes such RAM by
hard-linking the exact open backing handle into the staging directory as
`memory.bin`, after the state and the manifest are written and the handle is
flushed. Linux links the descriptor with `linkat(AT_EMPTY_PATH)` or through
`/proc/self/fd`, and Windows uses a handle-relative link; the link is then
opened relative to the staging directory and must have the device and inode
or file ID and the length of the source. Filesystems that cannot link fall
back to the independent copy. Once the link may exist, the staging directory
aliases live RAM: a failure removes it and flushes its parent before it
reports a rollback-safe error, and a failed cleanup is reported as
`SnapshotWriteError::CleanupUncertain`, which requires the source to
terminate instead of resuming.

Size new memory backing files through
`initialize_snapshot_memory_backing_file`, which keeps them sparse except
on Windows, where a dense file avoids slow copy-on-write faults, and add
`open_memory_backing_file_handle`, which returns the sized backing file
itself so that a caller can keep the exact handle for publication.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
Restoring a snapshot maps `memory.bin` as guest RAM, but a file backing
could only be mapped shared, so every guest write would go into the
snapshot artifact, and the snapshot could be used only once.

Add a file mapping mode to the memory manager. With
`FileMappingMode::CopyOnWrite`, an existing backing file is mapped with
private copy-on-write views, so the guest reads the contents of the file
while its writes stay private to the process and never reach the file.
Copy-on-write RAM requires an existing file backing, and it is not offered
as a shared-memory backing to other processes. The default mode is shared,
as before.

Also let the memory manager count guest-memory faults and the bytes
populated while resolving them, for diagnosing restore performance, and
report the totals through `MemoryManager::fault_counters`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
…he directory

Restore read `manifest.bin` and `state.bin` by joining each name to the
snapshot path, and the caller reopened `memory.bin` the same way. Every
open resolved the pathname again, following symbolic links, so renaming
or replacing the directory, or one of its files, between two opens could
combine artifacts of different generations. Unexpected files in the
directory were ignored, and the length of `memory.bin` was checked only
by the caller.

Add `OpenedSnapshot`, which opens the snapshot directory once and then
opens each artifact relative to that directory handle. Linux uses
`openat` with `O_NOFOLLOW`; Windows uses handle-relative, read-only opens
that share only reads and reject reparse points. `OpenedSnapshot::open`
reads the bounded manifest and saved state through the opened handles,
requires the directory to contain exactly `manifest.bin`, `state.bin`,
and `memory.bin` as regular files, checks the exact artifact lengths, and
records the generation of the opened memory file: its identity, length,
and, on Linux, its modification and change times. Accessors return the
manifest and state, check that the memory generation is unchanged,
duplicate the exact memory handle for a later mapping, and report
logical and allocated artifact sizes for profiling. `into_parts` moves
the open directory and artifact handles into the new
`SnapshotRestoreGuards`, so a later caller can keep the generation open
for the lifetime of a restored VM.

`restore::read_snapshot` now takes the expected guest memory size and
validates all three artifacts through an `OpenedSnapshot`, and
`read_snapshot_manifest`, `read_snapshot_with_memory`, and
`read_snapshot_artifacts_with_memory` cover manifest-only reads and reads
that also return the exact memory handle. The top-level `read_snapshot`
re-export is removed; its callers use `restore::read_snapshot`. The
command-line restore path still reopens `memory.bin` as shared, writable
guest RAM, until restores map it privately copy-on-write.

Document restore validation and the limits of the local format: it does
not authenticate same-length payload changes, and Linux file descriptors
do not provide mandatory write exclusion.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
…ion handles

`--restore-snapshot` reopened `memory.bin` by path for reading and writing
and mapped it as shared guest RAM. Every guest write after restore went
into the snapshot artifact, so a snapshot could be restored only once,
and copying the directory first was the only way to reuse it. The
restored VM also held no handle to the generation it had validated, so
the artifacts could be replaced while the VM ran.

Restore now opens the snapshot through an `OpenedSnapshot` before the VM
configuration is built, in the new `snapshot_restore` module of
`openvmm_entry`. Just before the VM worker launches, the opened generation
is validated against the VM configuration, a duplicate of the exact
opened memory handle becomes a private copy-on-write memory handle
(`file_to_copy_on_write_memory_fd`), and the memory generation is checked
again after the mapping handle is created. The worker receives the handle
with the new `shared_memory_copy_on_write` flag and maps guest RAM
privately copy-on-write, so guest writes stay in the process and never
reach `memory.bin`, and one snapshot can be restored any number of times.
The open directory and artifact handles move to the worker as the new
`snapshot_restore_guards` parameter, and the worker keeps them until it
exits, which keeps the generation open for the lifetime of the restored
VM; on Windows the read-only handles also deny writes to it.

The worker takes its restore inputs from the parameters in the new
`dispatch/restore` module, which also takes over the saved-state
inventory check. The management and petri launches pass the defaults: a
shared mapping and no guards.

With `OPENVMM_STARTUP_PROFILE` set, restore records the `restore` phases
`artifact_open`, `artifact_prepare`, and `cow_section_create`, and a
launch records the `startup` milestones `worker_launch` and
`worker_construct` and the worker phases `partition_prototype`,
`cow_map_view`, `partition_build`, `gpa_registration`, and
`partition_finalize`.

Document that restores are private and repeatable, and describe the
handles that restore retains.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
An orchestrator that restores a VM from a snapshot has no reliable way to
learn when the restore has finished: OpenVMM reports failures only in its
log, and a restored vCPU may already be running before the orchestrator
can tell whether devices started.

Add `--restore-ready-path <PATH>`, which requires `--restore-snapshot`.
Before the VM worker launches, OpenVMM connects to the named endpoint, an
existing Unix domain socket on Linux or a `//./pipe/...` named pipe on
Windows, and passes the connection to the worker as the new
`restore_ready_sink` parameter. After the saved state is applied, the
worker holds the restored VPs stopped. When the VM first starts, the
worker starts its state units, writes exactly `RESTORE_READY_EVENT_V1`
(`OPENVMM_RESTORE_READY_V1\n`) to the endpoint and flushes it, and only
then releases the VPs, so the event always precedes guest execution. A
failure to connect, write, or flush fails the launch, and a failure to
write or flush also stops the started units, leaving the VPs stopped.
The endpoint is used once and is never saved in a snapshot. The worker
keeps this restore state in `dispatch/restore`, which now sequences the
start of a restored VM.

With `--paused`, the first successful `resume` in the REPL publishes the
event. A resume that fails before then ends the REPL with an error and
tears the VM down, because the single-use endpoint may already be
consumed and a later resume would start the guest without the event.

With `OPENVMM_STARTUP_PROFILE` set, restore also records the `restore`
phases `saved_state_restore` and `device_start`, the latter with the
guest-memory faults and populated bytes counted while the devices start.

The management and petri launches pass no readiness endpoint.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
A WHP partition registers every guest RAM range with WHP when the range
is mapped, and asks WHP to deliver GPA access faults on mapped memory to
the VMM so that it can populate pages itself. For a VM restored from a
private copy-on-write snapshot mapping, both are wasted work: registering
a large memory range up front makes startup scale with guest memory, and
every first touch of a page costs a VP exit and a populate call even
though WHP can resolve the copy-on-write fault itself.

Add two partition-creation options to `ProtoPartitionConfig`.
`user_mode_memory_faults` keeps the current behavior when set; when it is
clear, WHP no longer requests `GpaAccessFaultExit`, and the partition
reports that it cannot resolve memory faults, so the memory backing does
not defer commits or protections to them. `lazy_memory_registration`
makes the WHP memory mapper register only the first 64 MiB of each
mapping when it is mapped. A guest access to a range that is not yet
registered reaches the VP as an unmapped-GPA access; the mapper then
registers the 2-MiB chunk containing it, and the VP retries the access.
The mapper tracks the registered ranges, so an overlay page removed from
an unregistered range is unmapped rather than remapped, and unmapping a
range unregisters only what was registered. Mapping failures in the
mapper are now returned instead of panicking.

The VM worker and the TMK VMM keep the current behavior: user-mode fault
resolution and eager registration. Backends other than WHP ignore both
options.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
…erialized kicks

The virtio transports save and restore only transport state: feature
negotiation, queue configuration, and queue progress. A device whose
state lives outside its queues, such as buffered console or network
data, has nowhere to save it, so such devices cannot support
save/restore. Restore also trusted the saved queue layout and progress,
and a queue notification signaled the queue event directly from the
transport, so a kick that raced with queue startup could be lost; this
is why the virtio-blk over virtio-mmio boot test was marked unstable.

Add device-private saved state to `VirtioDevice`. After all queues stop,
`save_device` returns an optional typed `SavedStateBlob`, which the PCI
and MMIO transports save as a new `device_state` field; `restore_device`
receives it, and `device_state_validator` returns an immutable validator
that the transport keeps after the device moves into its task. The
defaults preserve the current behavior: devices that support
save/restore have no private state, and other devices cannot be saved.
The new `device::saved_state` module holds the validator type and a
helper that reads the front restored descriptor without consuming it.

Restore now validates the saved state before it changes the transport:
exactly two feature banks, queue sizes within each queue's device
maximum, enabled rings aligned, inside guest RAM, and not overlapping,
available and used progress consistent with the rings, progress present
for every enabled queue of a DRIVER_OK device, and private state accepted
by the device's validator. The restored private state is then staged,
and the device task applies it on the first activation: the start of an
active device, DRIVER_OK, a device-configuration access, or a queue
kick. A configuration access whose activation fails completes with an
error, and the staged state is kept. `start_fallible` waits for the
activation and the queue starts, so a failure fails the VM start. A save
before activation returns the staged state unchanged, and queues that
were never started keep their restored progress. A guest reset of a
device that is not DRIVER_OK discards the staged state.

Queue notifications now reach the device task as serialized `Kick`
commands, ordered with private-state activation. Kicks coalesce until the
task dequeues them, and a kick for a queue that has not started is
staged and delivered once the queue starts, so no notification is lost.
The virtio-blk over virtio-mmio boot test is therefore no longer marked
unstable. The device task traces the restore lifecycle under the
`virtio_restore` target.

`SavedStateBlob` becomes `Clone`, so the staged state can be applied and
saved again.

The virtio-blk persistence test now shuts the guest down with an
enlightened shutdown after its save/restore pulse, avoiding the pipette
shutdown delay, which depends on the guest timer state that the test
just restored.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
A snapshot of a running VM first gates host input, so that no new
external input reaches a device while its state is captured, and resumes
the input if the save transaction fails. `ChangeDeviceState` provides
`quiesce_input` and `resume_input` for this, but the virtio transports
did not implement them, so a virtio device could not take part.

Add `quiesce_input` and `resume_input` to `VirtioDevice`, defaulting to
success for devices without host input. The PCI and MMIO transports
forward both through the device task as the new `QuiesceInput` and
`ResumeInput` commands, so they are ordered with the device's other
commands, and return the device's error.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
…t policies

A virtio-console always discarded guest output while its host backend
was disconnected, and a console resource could not describe how its host
endpoint is attached or reconnected. A host that must not lose console
output, or that reattaches endpoints after a restore, has no way to ask
for that, and the resolver cannot reject an inconsistent endpoint
description.

Add `VirtioConsoleDisconnectPolicy` and an optional
`VirtioConsoleAttachment` to `VirtioConsoleHandle`. With the `Retain`
policy, the console keeps pending guest transmit descriptors while the
backend is disconnected and forwards them after it reconnects; `Discard`
keeps the current behavior and remains the default of
`VirtioConsoleDevice::new`. The new `new_with_policy` constructor selects
the policy, and the worker now holds its backend in a
`ConsoleWorkerMode`. The attachment records a stable ID, the backend
kind, the attachment mode, the endpoint identity, and a reconnect policy
with its requirement and timeout. The resolver rejects an attachment
whose identifiers are empty or oversized, whose mode and reconnect policy
conflict, or whose requirement and timeout do not match the policy: a
recreated listener or a discarding attachment is optional without a
timeout, a reconnecting client is required with a bounded timeout, and an
inherited attachment is required without a timeout.

Disconnects are also handled consistently: after a read error or
end-of-file, the worker completes the backend's disconnect before it
waits for a new connection, and a backend write that accepts no bytes is
treated as a disconnect instead of a panic.

All existing console resources use `Discard` without an attachment.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
A virtio-console could not be saved: host input already read from the
backend lived only in a local buffer of the receive loop, the worker
state was removed once both queues stopped, and the progress within the
current transmit descriptor was lost with it. Restoring a console would
therefore drop accepted input or replay output.

Implement device-private save and restore for the direct console mode.
The worker state is now created with the device and kept while the
queues are stopped, so a stopped device still holds its private state.
Host input is staged in the worker state before it is copied to a guest
receive buffer, so input accepted from the backend survives worker
restarts and snapshots; the receive loop also reads the backend before
the guest posts a buffer, completing zero-length buffers as it goes. The
saved state, schema version 1, records the console size, the bytes
already written from the current transmit descriptor, the staged input,
and the disconnect policy. Restore validates the encoded size, the
schema version, the policy, and the bounds of every field before
decoding further, and requires a restored transmit offset to lie within
the front descriptor of the enabled transmit queue, both before the
queues restart and when the transmit queue starts. Reset clears the
private state.

The console also implements `quiesce_input` and `resume_input`: while
input is gated, the worker reads no host input, and output continues.

`SavedStateBlob::encoded_len` returns the encoded payload length without
decoding it, which the console uses to bound its private state.

Document that virtio-console now supports save and restore through the
transport's device-private payload.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
To save a network device, its frontend must stop exchanging packets with
the endpoint at a well-defined point: every receive and transmit already
accepted by the endpoint must complete, and the endpoint must not keep
any guest buffer. The `Queue` trait had no operation for this, so a
frontend could only drop its queues, losing work in flight.

Add `Queue::quiesce`, which finishes the work that a queue already
accepted without accepting new work. On success, all resulting receive
and transmit completions are available through `rx_poll` and `tx_poll`,
the queue retains no buffer IDs or guest-memory segments, and the
returned `QueueQuiesceResult` reports how many completions of each kind
remain to be polled. `Queue::resume` resumes host receive admission after
a rolled-back quiesce. Queues do not support quiesce by default, and
resume succeeds by default.

`BufferAccess` now requires `Send`, so a quiesce future can hold the
frontend's buffer pool across await points; every existing
implementation already is.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
A TAP queue wrote guest packets synchronously and completed every
packet, including the ones that the TAP interface dropped because its
buffer was full or the interface was down. A packet dropped under
backpressure was therefore lost, and the queue could not implement the
endpoint quiesce contract, because it could not report which transmits
were still in flight.

Each `tx_avail` call now writes at most one guest packet. When the TAP
interface applies backpressure, the queue keeps the packet, and with it
the ownership of its descriptors, until `poll_ready` writes it through
the new `AsyncWrite` implementation of `PolledTap`; `tx_poll` then
completes it. A partial write or a write error is reported as a fatal
transmit error instead of being ignored.

Implement `Queue::quiesce` for TAP queues: a quiesced queue drops its
free receive buffers, stops reading from the interface and admitting
receive buffers, finishes writing its pending packet, and reports the
receive and transmit completions that it still holds. `Queue::resume`
resumes receiving after a rolled-back quiesce.

The TAP backpressure test now checks that every packet is completed
exactly once instead of being dropped.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
A Consomme queue could not stop exchanging packets at a well-defined
point for a save, because it always polled the Consomme stack and its
control channels and kept accepting receive buffers.

Implement `Queue::quiesce` for Consomme queues. A quiesced queue
processes the frames that the guest already queued for transmit, drops
its posted receive buffers, ignores new ones, and stops polling the
Consomme stack and the control channels; `poll_ready` then only reports
the completions still pending. Quiesce reports the receive and transmit
completions that the queue still holds, and `Queue::resume` restores
normal polling after a rolled-back quiesce.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
Consomme derives the guest and gateway addresses from a CIDR, so a host
cannot give a guest an exact, preconfigured IPv4 identity: a specific
guest address and prefix, and a gateway whose address and MAC address
stay fixed across snapshots and restores. Consomme also always treats
traffic to the gateway as ordinary egress, with no way to map it onto
host loopback, which a guest with a static identity uses to reach host
services.

Add `ConsommeParams::set_static_ipv4`, which sets an exact guest address,
prefix length, gateway address, and gateway MAC address. The identity
must be consistent: a prefix of 1 to 30 bits, a guest address that is
neither the network nor the broadcast address, and a gateway at the
first host address of the network, distinct from the guest. A static
identity stops advertising a routable IPv6 prefix and enables the new
`map_gateway_to_host_loopback` parameter, which translates guest TCP and
UDP flows addressed to the gateway onto host loopback. With the mapping
disabled, such flows are dropped, except TCP to the single port named by
the new `gateway_loopback_proxy_port` parameter.

Expose the identity to resources: `ConsommeHandle` gains an optional
`StaticIpv4Config`, which is mutually exclusive with `cidr`, and optional
overrides of host-local access, the gateway mapping, and the gateway
proxy port. The resolver rejects a handle with both a CIDR and a static
identity, or with an inconsistent identity. Existing handles set none of
the new fields.

This changes one behavior of every Consomme endpoint: a guest TCP or UDP
flow addressed to the gateway, other than the gateway's own DNS and DHCP
services, used to be sent to the gateway's address on the host network,
where no gateway exists. Such flows are now dropped unless the gateway
mapping or the proxy port allows them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
virtio-net claimed to support save and restore unconditionally, but the
transport saved only queue progress: packets that the endpoint had
accepted, or that the device held for the guest, were lost, and nothing
tied a restored guest's view of its network identity to the endpoint.
Saving a VM with virtio-net therefore silently corrupted the guest's
network state.

Save and restore are now opt-in. `NicBuilder::save_restore` binds a
device to an exact static IPv4 identity and a fixed effective feature
contract, and `VirtioNetHandle` gains `save_restore`, `static_ipv4`, and
`effective_features` fields for resources; a resource that opts in must
supply both. Other devices report that they do not support save and
restore.

When the transport stops an active queue pair of such a device, the
device quiesces the endpoint queues, drains every completion the
endpoint still holds so that no descriptor stays in flight, and captures
the progress of both queues, which the transport then collects queue by
queue. The device-private saved state, schema version 1, records the
identity, the guest MAC address, the link state, the effective and
negotiated feature banks, the endpoint offloads, the queue-pair
lifecycle, and an endpoint generation that each restore advances.
Restore validates it against the destination device: the identity must
match and use MAC addresses derived from its IPv4 addresses, the
effective features and endpoint offloads must be unchanged, the
negotiated features must stay within the contract and match the saved
banks, and the device must have a single queue pair whose restored
queues own no descriptors. Restarting a stopped pair requires the same
negotiated features.

The device also implements `quiesce_input` and `resume_input`: while
input is gated, the endpoint queues finish the work they already accepted
but admit no new host receive traffic, and the gate is re-established
whenever the queues restart. Workers are now started only by the
coordinator, after it establishes the gate.

Petri records the PCIe ports of virtio-net devices added without a save
and restore contract and skips its startup save/restore test while any
of them is configured. Every existing virtio-net resource leaves
`save_restore` unset.

Document that virtio-net supports save and restore only with a static
identity and feature contract.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
A FUSE session negotiates its protocol version, feature flags, and
limits with the guest during FUSE_INIT and keeps them only in memory. A
file system device built on it could not be saved and restored, because
a restored session would be uninitialized while the guest driver
continued with its negotiated protocol.

Add `SessionState`, a serializable snapshot of whether a session is
initialized and of its negotiated values, which contains no file handle,
task, or native resource. `Session::save_state` captures it, and
`Session::restore_state` restores it after validating it: an
uninitialized state must carry no negotiated values, and an initialized
one must use a supported protocol version (7.27 through the
implementation's minor version), accept only feature flags that the
guest advertised, keep its extended-init flags consistent, and have
nonzero write and time-granularity limits. The types are re-exported
from the crate root.

Two related fixes make the saved values well-defined. FUSE_INIT now
records the negotiated minor version as the lower of the guest's and the
implementation's, instead of the guest's value, so a session never
claims a protocol newer than it implements. Destroying a session also
resets its negotiated information, so a later session starts from
defaults.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
A virtio-fs inode records a single host path, which it uses for every
later operation on the node. That path went stale in several cases. A
hard link gave the inode a second name that nothing recorded, so
unlinking the original name left the inode pointing at a missing path
even though it was still reachable through the link. Renaming a
directory left every open inode below it pointing at the old location.
The dedup map, which lets repeated lookups of a host file return one
FUSE node, only evicted the renamed or removed path, without updating
the inodes that referred to it.

Each inode now keeps the set of host paths through which the guest
knows it. Lookups and hard links add a path. After an unlink, the paths
at or below the removed name are dropped from every inode of the volume,
and an inode whose primary path was removed switches to one of its
remaining aliases. After a rename, the paths at or below the old name
are rewritten under the new name, after first dropping the aliases of
any overwritten destination. The dedup map is then rebuilt from the
surviving inodes, and a path-keyed inode with no remaining alias is left
out of it, so a later create at a vacated path still gets a fresh node.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
Linux 5.0 added `acpi_rsdp_addr` to the zero page: a 64-bit field at
offset 0x70 of `boot_params` through which a boot loader hands the
kernel the physical address of the ACPI RSDP, so the kernel need not
scan legacy memory for it. `boot_params` still declared those bytes as
part of the 16-byte `_pad3`, so a loader could not set the field.

Split `_pad3` into `acpi_rsdp_addr` and the remaining 8 bytes of
padding, matching the kernel's layout. The structure's size and the
offsets of all other fields are unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
`load_static_elf` allocates a page-aligned chunk buffer for every call
and uses it to import the file-backed parts of each segment. A loader
that imports several images in a row, such as a kernel followed by an
initial RAM disk, therefore allocates and frees that buffer once per
image.

Split the import into a crate-private `load_static_elf_with_buffer`,
which takes the chunk buffer from its caller, and keep
`load_static_elf` as a wrapper that allocates a buffer for a single
call. Behavior is unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
A guest booted directly into a Linux kernel, without firmware and
without ACPI, learns its processors and interrupt routing from Intel
MultiProcessor Specification 1.4 tables. The loader had no way to build
them.

Add the `mptable` module, whose `build` function returns an MP floating
pointer, placed at guest address 0, and a configuration table, placed at
0x400, for a given set of APIC IDs and level-triggered ISA IRQs. The
configuration table describes each processor, with the first as the
bootstrap processor, one ISA bus, one I/O APIC at 0xfec00000, and the
routing of the 15 ISA IRQs other than the cascade, with IRQ 0 on pin 2;
IRQs listed as level-triggered are described as active-high, and the
others use bus defaults. APIC IDs must be contiguous from zero and fit
the 8-bit MP processor entry, IRQs must be in the ISA range, and both
structures carry valid checksums.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef640993-0003-4e49-b1ed-1b8b4776d0ef
ppenna and others added 3 commits October 8, 2026 02:38
Reject copy-on-write scratch capture where restore cannot materialize it, document direct-claimed hard-link requirements, and cover clone isolation plus exact claimed-file identity.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A cold boot with --cpu-profile auto, the default, failed with
E_PROFILE_HOST_UNKNOWN on every CPU that no pinned profile serves, such
as Cascade Lake, Sapphire Rapids, Granite Rapids, Tiger Lake, Meteor
Lake, and AMD's Ryzen CPUs, including the Granite Rapids runners of
Copilot cloud agent sessions. --cpu-profile host booted such Intel and
AMD hosts, but only after the user opted in following the failure
(microsoft/nvx#434).

select_on_host now falls back there. Where no pinned profile's
generation covers an Intel or AMD host CPU (the new
cpu_profile::pinned_profile_serves), auto derives the host profile as
--cpu-profile host does: the VM worker fingerprints the backend, derives
<vendor>.host.v1 under the pinned profiles' policy, and checks its
generation. A CPU of another vendor, such as Hygon's, still fails with
E_PROFILE_HOST_UNKNOWN, and so does a CPU that pinned profiles of more
than one generation serve, a catalog defect, whose error still names
--cpu-profile host. auto does not fall back where the backend does not
support the CPU's pinned profile (E_PROFILE_UNSUPPORTED): that reveals a
profile or hypervisor problem on a served generation, and the error
still names --cpu-profile host. Explicit profile IDs, --cpu-profile
host, restores, which take the snapshot's profile, and
--cpu-fingerprint, whose check never falls back, are unchanged.

The fallback is not silent. Each cold boot that takes it logs one
warning at the default log level, from the VM worker, so wherever auto
is selected, the management endpoint included. It starts with the stable
marker NVX-CPU-PROFILE-FALLBACK:, names the host CPU (vendor, display
family/model/stepping, and brand string), the host profile and its
digest, and the limits of a host profile, including what that cold
boot's fingerprint and derivation took, and asks the user to attach the
CPU's fingerprint (openvmm --hypervisor <backend> --cpu-fingerprint
fingerprint.json) to NVX's CPU profile request form, through a link
that prefills the form's title and CPU fields. An explicit --cpu-profile
host warns with the same profile, digest, and limits, and with the same
request where no pinned profile serves the CPU.

Where the fallback cannot boot, its errors keep their codes and say that
auto fell back: a failed fingerprint or derivation, such as a backend
that lacks a CPU feature that the time ABI requires
(E_PROFILE_UNSUPPORTED), through a typed context, and a CPU profile
failure of the partition (E_PROFILE_UNSUPPORTED, E_PROFILE_TIME_BITS,
E_CPU_SURFACE, or E_CPU_UNLISTED), through the cold boot's hint, which
no longer suggests --cpu-profile host there and does not repeat the
fallback's context.

The host-cpu-unknown time ABI test hook makes auto treat the host CPU as
one that no pinned profile serves, so that CI's runners, which pinned
profiles serve, exercise the fallback. Like every hook, it sets
TEST_HOOKS in the restore packet and the time samples.

Unit tests cover the selection on Intel, AMD, and Hygon CPUs with and
without the hook, the fallback and its warning, the explicit host
profile's warning with and without the request, the request link's
encoding, a fallback that cannot boot (a failed fingerprint, a
derivation without RDTSCP, and a later E_PROFILE_UNSUPPORTED), the
restore of a fallback's snapshot under any hooks, the catalog
predicate, and the hook's parsing. The Guide documents the fallback and
its warning in the CLI reference and the snapshots page.

On a Core i9-12900H with WHP, which intel.alderlake.v1 serves, the hook
made a cold boot fall back to intel.host.v1 with the warning; its guest
passed the time ABI checks, and its snapshot restored on intel.host.v1
with auto. The package-scoped clippy, documentation, and unit-test gates
(cpu_profile, virt, openvmm_core, openvmm_entry, and openvmm_helpers)
and xtask fmt pass.

Part of microsoft/nvx#434.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Allow exactly one read-only distro block with the explicit nvx_overlay_upper=ramfs kernel token, and reject any other nvx_overlay_upper token, including a bare or repeated one. Keep existing scratch layouts unchanged and reject snapshot or restore for the scratchless profile. Document the layout and its restrictions in the Guide.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 8, 2026 17:30
@esaurez
esaurez force-pushed the esaurez/microvm-image-slots branch from 5ffe5c2 to 41a595e Compare October 8, 2026 17:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The new capability is exposed in debug logs, and host-control framing has reliability and validation defects.

5 open findings
What changed in this PR

Adds four bind-once, read-only image slots to microVM ABI 2, including fixed virtio topology, authenticated host control, and snapshot/restore support.

Changes:

  • Adds empty/bind-once virtio-blk image-slot devices and configuration notifications.
  • Extends CLI, management RPC, restore packets, validation, and snapshot contracts.
  • Documents image-slot topology and the host-control protocol.
File Description
vm/​devices/​virtio/​virtio/​src/​transport/​task.rs Polls device configuration changes.
vm/​devices/​virtio/​virtio/​src/​transport/​core.rs Signals configuration-change interrupts.
vm/​devices/​virtio/​virtio/​src/​device.rs Adds configuration-change polling API.
vm/​devices/​virtio/​virtio_resources/​src/​lib.rs Defines image-slot resources and RPCs.
vm/​devices/​virtio/​virtio_resources/​Cargo.toml Adds error dependency.
vm/​devices/​virtio/​virtio_blk/​src/​resolver.rs Resolves image-slot devices.
vm/​devices/​virtio/​virtio_blk/​src/​lib.rs Implements empty and bind-once media.
vm/​devices/​virtio/​virtio_blk/​src/​integration_tests.rs Tests empty-slot I/O errors.
vm/​devices/​virtio/​virtio_blk/​Cargo.toml Adds image-slot dependencies.
vm/​devices/​chipset/​src/​microvm.rs Updates restore-packet tests.
vm/​devices/​chipset_resources/​src/​microvm_time.rs Adds image-slot restore targets.
openvmm/​openvmm_ttrpc_vmservice/​src/​vmservice.proto Exposes image-slot launch fields.
openvmm/​openvmm_helpers/​src/​snapshot/​microvm.rs Extends snapshot contracts and validation.
openvmm/​openvmm_entry/​src/​vm_controller/​microvm.rs Excludes binding during capture.
openvmm/​openvmm_entry/​src/​vm_controller.rs Runs the host-control service.
openvmm/​openvmm_entry/​src/​ttrpc/​mod.rs Passes the driver during finalization.
openvmm/​openvmm_entry/​src/​ttrpc/​microvm.rs Handles management-RPC slot configuration.
openvmm/​openvmm_entry/​src/​storage_builder/​microvm.rs Builds fixed image-slot devices.
openvmm/​openvmm_entry/​src/​storage_builder.rs Stores image-slot configuration.
openvmm/​openvmm_entry/​src/​serial_io/​microvm.rs Binds secure host-control endpoints.
openvmm/​openvmm_entry/​src/​microvm/​restore.rs Carries and validates restore targets.
openvmm/​openvmm_entry/​src/​microvm/​network.rs Updates discovery call sites.
openvmm/​openvmm_entry/​src/​microvm/​mod.rs Adds host-control resources.
openvmm/​openvmm_entry/​src/​microvm/​launch.rs Propagates image-slot configuration.
openvmm/​openvmm_entry/​src/​microvm/​host_control.rs Implements host-control protocol v1.
openvmm/​openvmm_entry/​src/​microvm/​filesystem.rs Updates discovery call sites.
openvmm/​openvmm_entry/​src/​microvm/​console.rs Shares control authentication.
openvmm/​openvmm_entry/​src/​microvm/​config.rs Configures slots and host control.
openvmm/​openvmm_entry/​src/​cli_args/​microvm.rs Adds and validates CLI options.
openvmm/​openvmm_defs/​src/​microvm.rs Defines topology and machine validation.
openvmm/​openvmm_core/​src/​worker/​dispatch/​microvm.rs Assigns slot MMIO and IRQ resources.
Guide/​src/​user_guide/​openvmm/​snapshots.md Documents snapshot semantics.
Guide/​src/​SUMMARY.md Links the host-control page.
Guide/​src/​reference/​openvmm/​management/​host_control_protocol.md Documents protocol v1.
Guide/​src/​reference/​openvmm/​management/​grpc.md Documents RPC restore activation.
Guide/​src/​reference/​openvmm/​management/​cli.md Documents CLI and topology.
Cargo.lock Records dependency updates.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread openvmm/openvmm_ttrpc_vmservice/src/vmservice.proto
Comment thread openvmm/openvmm_entry/src/microvm/host_control.rs Outdated
Comment thread openvmm/openvmm_entry/src/microvm/host_control.rs
Comment thread openvmm/openvmm_entry/src/ttrpc/microvm.rs
Comment thread openvmm/openvmm_entry/src/vm_controller/microvm.rs Outdated
ppenna and others added 2 commits October 9, 2026 10:59
microVM L3/L4 egress rules took two forms: IPv4[/PREFIX], which matches
every IPv4 protocol, and IPv4[/PREFIX]:tcp|udp:PORT, which matches one
TCP or UDP destination port. A rule could not select one protocol on
every port, nor ICMP at all, so policies such as "allow TCP to
192.0.2.0/24 on any port" or "deny ICMP to 192.0.2.7" could not be
expressed (microsoft/nvx#277).

EgressRule now also parses IPv4[/PREFIX]:tcp, IPv4[/PREFIX]:udp, and
IPv4[/PREFIX]:icmp. A TCP or UDP rule without a port matches every
destination port of its protocol. An ICMP rule matches every ICMP
message and takes no port: IPv4[/PREFIX]:icmp:PORT fails with the new
ParseEgressRuleError::IcmpPort. EgressTransport gains Icmp, and
EgressRule::port is zero for every rule without a port. "any" is not a
native protocol: a caller expresses any protocol on one port as one TCP
rule and one UDP rule.

A rule matches a packet whose destination its prefix contains and,
unless the rule is address-only, whose IPv4 protocol is the rule's and,
for a port-specific rule, whose TCP or UDP destination port is the
rule's. Deny rules still take precedence over allow rules, and the
default action still decides the traffic that matches neither list.

Every IPv4 fragment carries its protocol, but later fragments carry no
transport header. Rules without a port therefore classify fragments by
their protocol, and only a port-specific rule still makes a policy
reject every fragment, as any TCP or UDP rule did before. Policies of
address-only and port-specific rules behave as before, and ICMP and
other protocols match only address-only rules unless an ICMP rule
selects them.

allows_gateway_dns counts a protocol-wide TCP or UDP rule for the
gateway as it counts a rule for port 53, and does not count an ICMP
rule. ARP for a destination stays denied only by an address-only deny
rule, because a protocol-wide deny rule leaves the destination's other
protocols reachable. The canonical encoding gives ICMP the transport
byte 3 and port-free TCP and UDP rules the port 0, so the snapshot
policy digest tells every new rule apart, while the bytes of existing
policies, and their encoding version, are unchanged.

openvmm_entry's --network-egress-allow and --network-egress-deny
document the new forms. The CLI's L3/L4 rule test was nested inside
another test function and never ran; it now runs, beside a new test of
the protocol-wide forms and of the forms that are rejected before VM
resources are opened.

Unit tests cover the parser's new forms and errors, each protocol
selector as an allow rule and as a deny rule that overrides an allow
rule, a protocol-wide rule composed with a port rule in both
directions, fragments under port-free and port-specific policies, the
canonical encoding of each form, and the gateway DNS and ARP decisions.
The Guide documents the forms in the CLI reference and on the Consomme
backend page.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
microVM L3/L4 egress rules could select one TCP or UDP destination port,
or every port of a protocol, but not a range of ports. A policy that
admits or blocks a span of ports, such as MXC's network.egress selector
{"protocol": "tcp", "port": 8000, "endPort": 8010}, needed one rule per
port, and each rule list accepts at most 256 rules, so a range of more
than 256 ports could not be expressed at all (microsoft/nvx#278).

EgressRule now also parses IPv4[/PREFIX]:tcp:FIRST-LAST and
IPv4[/PREFIX]:udp:FIRST-LAST, which match every destination port from
FIRST through LAST, both included. Both ports must be in 1 through
65535. A range that ends below its first port fails with the new
ParseEgressRuleError::ReversedPortRange, and one that lacks its first or
last port, or names more than two, with InvalidPortRange. A range of one
port is that port's rule, so FIRST-FIRST and FIRST parse, deduplicate,
and encode alike. The new EgressRule::end_port returns the last port of
a range, the port of a single-port rule, and zero for a rule without a
port.

A port-specific rule matches an unfragmented TCP or UDP packet of its
protocol whose destination port lies in its range. Deny rules still take
precedence over allow rules, so a deny rule for one port inside an
allowed range blocks that port alone. A range restricts ports, so, like
a single-port rule, it makes a policy reject IPv4 fragments, and
allows_gateway_dns counts a TCP or UDP range for the gateway that
contains port 53.

The canonical encoding gives a TCP range the transport byte 4 and a UDP
range the transport byte 5, followed by its first and last ports, so the
snapshot policy digest of every encoding version tells a range apart
from its first port and from every other rule, while the bytes of
existing policies, and their encoding version, are unchanged.
EgressPolicy::bind now also rejects rules that parsing cannot produce,
such as a reversed range or an ICMP rule with a port, with
NonCanonicalRules, so validate catches them after a policy crosses a
boundary.

openvmm_entry's --network-egress-allow and --network-egress-deny
document the range form, and the Guide documents it in the CLI reference
and on the Consomme backend page.

Unit tests cover the parser's range forms and errors; each range's first
and last ports, a port inside it, and the ports next to it, for TCP and
UDP, in allow rules and in deny rules that override an allow rule; a
denied port inside an allowed range; fragments and gateway DNS under
ranges; the canonical encoding of ranges in every encoding version; the
rejection of rule forms that parsing cannot produce; and the CLI's
parsing of ranges, and rejection of malformed ones, before VM resources
are opened.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@esaurez
esaurez force-pushed the esaurez/microvm-image-slots branch from 41a595e to c713731 Compare October 9, 2026 19:09
@esaurez
esaurez changed the base branch from main to fix/egress-protocol-rules-277 October 9, 2026 19:09
esaurez added a commit to microsoft/nvx that referenced this pull request Oct 9, 2026
Pin OpenVMM c7137315 (nanvix/openvmm#116), which adds four optional
bind-once read-only image-slot transports to the microVM as part of
ABI 2. nanvix/openvmm#116 is stacked on nanvix/openvmm#124 (3796991d),
the current dev pin, so this promotion adds exactly its four commits:

- c071b869 virtio_blk: add bind-once image slots
- 0795babf microvm: add bind-once read-only image slots
- 957b1472 microvm: fold image slots into ABI 2
- c7137315 microvm: harden host-control framing, logging, and cleanup

A machine that does not declare image slots keeps its device topology,
and every machine and snapshot keeps microVM ABI 2, so
SOURCE-MANIFEST.json and the benchmark history dimensions are unchanged.

- Image slot 0 shares the second virtio-fs slot's MMIO window and
  shared-status word, and slot 1 its IRQ 13, so a slot-declaring launch
  attaches at most one live share.
- The restore packet carries an image-slot target in header byte 7,
  flagged by bit 4. nvx-time validates and reports it, never finishes
  such a restore itself, and nvx-snapshot activates the requested prefix
  before acknowledging the gated restore.
- The shared-status kernel patch maps slots 1 to 3 to their status words.
- Managed sandboxes can declare slots at provision time (configuration
  format 6), boot without a distro layer, and query or bind slots
  through the authenticated host-control channel. Only a slot-declaring
  sandbox records a host-control endpoint, and stop and deprovision
  remove its socket.
- The image-slot scenario and boot-cost benchmark hand OpenVMM its
  capability through a prepared pipe, as the other launchers do (#440).
- The Azure Linux initramfs installs nvx-image-slot.
- Add an image-slot correctness scenario and a boot-cost benchmark.

nvx.py verify passes with this pin.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
esaurez added a commit to microsoft/nvx that referenced this pull request Oct 9, 2026
Pin OpenVMM c7137315 (nanvix/openvmm#116), which adds four optional
bind-once read-only image-slot transports to the microVM as part of
ABI 2. nanvix/openvmm#116 is stacked on nanvix/openvmm#124 (3796991d),
the current dev pin, so this promotion adds exactly its four commits:

- c071b869 virtio_blk: add bind-once image slots
- 0795babf microvm: add bind-once read-only image slots
- 957b1472 microvm: fold image slots into ABI 2
- c7137315 microvm: harden host-control framing, logging, and cleanup

A machine that does not declare image slots keeps its device topology,
and every machine and snapshot keeps microVM ABI 2, so
SOURCE-MANIFEST.json and the benchmark history dimensions are unchanged.

- Image slot 0 shares the second virtio-fs slot's MMIO window and
  shared-status word, and slot 1 its IRQ 13, so a slot-declaring launch
  attaches at most one live share.
- The restore packet carries an image-slot target in header byte 7,
  flagged by bit 4. nvx-time validates and reports it, never finishes
  such a restore itself, and nvx-snapshot activates the requested prefix
  before acknowledging the gated restore.
- The shared-status kernel patch maps slots 1 to 3 to their status words.
- Managed sandboxes can declare slots at provision time (configuration
  format 6), boot without a distro layer, and query or bind slots
  through the authenticated host-control channel. Only a slot-declaring
  sandbox records a host-control endpoint, and stop and deprovision
  remove its socket.
- The image-slot scenario and boot-cost benchmark hand OpenVMM its
  capability through a prepared pipe, as the other launchers do (#440).
- The Azure Linux initramfs installs nvx-image-slot.
- Add an image-slot correctness scenario and a boot-cost benchmark.

nvx.py verify passes with this pin.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
esaurez added a commit to microsoft/nvx that referenced this pull request Oct 9, 2026
Pin OpenVMM c7137315 (nanvix/openvmm#116), which adds four optional
bind-once read-only image-slot transports to the microVM as part of
ABI 2. nanvix/openvmm#116 is stacked on nanvix/openvmm#124 (3796991d),
the current dev pin, so this promotion adds exactly its four commits:

- c071b869 virtio_blk: add bind-once image slots
- 0795babf microvm: add bind-once read-only image slots
- 957b1472 microvm: fold image slots into ABI 2
- c7137315 microvm: harden host-control framing, logging, and cleanup

A machine that does not declare image slots keeps its device topology,
and every machine and snapshot keeps microVM ABI 2, so
SOURCE-MANIFEST.json and the benchmark history dimensions are unchanged.

- Image slot 0 shares the second virtio-fs slot's MMIO window and
  shared-status word, and slot 1 its IRQ 13, so a slot-declaring launch
  attaches at most one live share.
- The restore packet carries an image-slot target in header byte 7,
  flagged by bit 4. nvx-time validates and reports it, never finishes
  such a restore itself, and nvx-snapshot activates the requested prefix
  before acknowledging the gated restore.
- The shared-status kernel patch maps slots 1 to 3 to their status words.
- Managed sandboxes can declare slots at provision time (configuration
  format 6), boot without a distro layer, and query or bind slots
  through the authenticated host-control channel. Only a slot-declaring
  sandbox records a host-control endpoint, and stop and deprovision
  remove its socket.
- The image-slot scenario and boot-cost benchmark hand OpenVMM its
  capability through a prepared pipe, as the other launchers do (#440).
- The Azure Linux initramfs installs nvx-image-slot.
- Add an image-slot correctness scenario and a boot-cost benchmark.

nvx.py verify passes with this pin.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
esaurez added a commit to microsoft/nvx that referenced this pull request Oct 9, 2026
Pin OpenVMM c7137315 (nanvix/openvmm#116), which adds four optional
bind-once read-only image-slot transports to the microVM as part of
ABI 2. nanvix/openvmm#116 is stacked on nanvix/openvmm#124 (3796991d),
the current dev pin, so this promotion adds exactly its four commits:

- c071b869 virtio_blk: add bind-once image slots
- 0795babf microvm: add bind-once read-only image slots
- 957b1472 microvm: fold image slots into ABI 2
- c7137315 microvm: harden host-control framing, logging, and cleanup

A machine that does not declare image slots keeps its device topology,
and every machine and snapshot keeps microVM ABI 2, so
SOURCE-MANIFEST.json and the benchmark history dimensions are unchanged.

- Image slot 0 shares the second virtio-fs slot's MMIO window and
  shared-status word, and slot 1 its IRQ 13, so a slot-declaring launch
  attaches at most one live share.
- The restore packet carries an image-slot target in header byte 7,
  flagged by bit 4. nvx-time validates and reports it, never finishes
  such a restore itself, and nvx-snapshot activates the requested prefix
  before acknowledging the gated restore.
- The shared-status kernel patch maps slots 1 to 3 to their status words.
- Managed sandboxes can declare slots at provision time (configuration
  format 6), boot without a distro layer, and query or bind slots
  through the authenticated host-control channel. Only a slot-declaring
  sandbox records a host-control endpoint, and stop and deprovision
  remove its socket.
- The image-slot scenario and boot-cost benchmark hand OpenVMM its
  capability through a prepared pipe, as the other launchers do (#440).
- The Azure Linux initramfs installs nvx-image-slot.
- Add an image-slot correctness scenario and a boot-cost benchmark.

nvx.py verify passes with this pin.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Base automatically changed from fix/egress-protocol-rules-277 to main October 9, 2026 19:54
esaurez and others added 4 commits October 9, 2026 12:56
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A microVM may declare four bind-once read-only virtio-blk image slots,
which select microVM ABI 3. A VM without slots keeps ABI 2, its topology,
discovery tokens, and snapshot format.

Slots image0-image3 use MMIO 0xd0008000-0xd000b000, ISA edge IRQs
1/13/14/15, and shared-status words 0x20-0x2c. The first slot reuses the
second virtio-fs slot's window and status word, and the second slot its
interrupt, so a VM with image slots attaches at most one filesystem.

--microvm-image-slot-boot-count activates a prefix of the slots at cold
boot. A restore may activate a longer prefix with --restore-image-slots;
the target travels in restore packet version 4 as flag bit 4 and header
byte 7, and holds the post-restore gate until the guest acknowledges. The
management RPC carries the same target.

A host-control endpoint (--microvm-host-control), authenticated with the
control-console capability, queries the slots and binds a validated
regular file into an empty slot once. A clean close at a frame boundary
ends a session normally.

Machine validation admits exactly the declared image-slot transports, and
the snapshot contract records the slot capacity and the boot prefix.
Capture is rejected while a slot is bound.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep image slots optional within ABI 2 instead of allocating a separate ABI version. Validate the slot-specific snapshot metadata and discovery independently, including platform-tier snapshots, while preserving the no-slot device layout.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address review findings in the image-slot host-control service:

- Redact the host-control capability when the management RPC logs a
  CreateVM request. The capability authenticates the endpoint.
- Apply the idle timeout to a request frame's payload as well as its
  length, so an authenticated client that stalls inside a frame cannot
  hold the single-connection endpoint.
- Keep responses within the 64 KiB frame limit. A response that would
  exceed it, such as an error that echoes a long path, becomes a bounded
  response_too_large error for the same request.
- Reject a management-RPC image_slot_boot_count without image_slots, as
  the command line already does.
- After a committed snapshot, remove the host-control socket with the
  console sockets and fail the source process if that removal fails,
  instead of leaving it to the cleanup guard. Drop the unreachable
  duplicate removal from the error path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@esaurez
esaurez force-pushed the esaurez/microvm-image-slots branch from c713731 to 67b641d Compare October 9, 2026 20:20
esaurez added a commit to microsoft/nvx that referenced this pull request Oct 9, 2026
Pin OpenVMM 67b641d8 (nanvix/openvmm#116), which adds four optional
bind-once read-only image-slot transports to the microVM as part of
ABI 2. nanvix/openvmm#116 is based on OpenVMM main f4566bab, the current
dev pin, so this promotion adds exactly its four commits:

- f5c8dab0 virtio_blk: add bind-once image slots
- 0a3dc07e microvm: add bind-once read-only image slots
- 4eb09487 microvm: fold image slots into ABI 2
- 67b641d8 microvm: harden host-control framing, logging, and cleanup

A machine that does not declare image slots keeps its device topology,
and every machine and snapshot keeps microVM ABI 2, so
SOURCE-MANIFEST.json and the benchmark history dimensions are unchanged.

- Image slot 0 shares the second virtio-fs slot's MMIO window and
  shared-status word, and slot 1 its IRQ 13, so a slot-declaring launch
  attaches at most one live share.
- The restore packet carries an image-slot target in header byte 7,
  flagged by bit 4. nvx-time validates and reports it, never finishes
  such a restore itself, and nvx-snapshot activates the requested prefix
  before acknowledging the gated restore.
- The shared-status kernel patch maps slots 1 to 3 to their status words.
- Managed sandboxes can declare slots at provision time (configuration
  format 6), boot without a distro layer, and query or bind slots
  through the authenticated host-control channel. Only a slot-declaring
  sandbox records a host-control endpoint, and stop and deprovision
  remove its socket.
- The image-slot scenario and boot-cost benchmark hand OpenVMM its
  capability through a prepared pipe, as the other launchers do (#440).
- The Azure Linux initramfs installs nvx-image-slot.
- Add an image-slot correctness scenario and a boot-cost benchmark.

nvx.py verify passes with this pin.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
esaurez added a commit to microsoft/nvx that referenced this pull request Oct 9, 2026
Pin OpenVMM 67b641d8 (nanvix/openvmm#116), which adds four optional
bind-once read-only image-slot transports to the microVM as part of
ABI 2. nanvix/openvmm#116 is based on OpenVMM main f4566bab, the current
dev pin, so this promotion adds exactly its four commits:

- f5c8dab0 virtio_blk: add bind-once image slots
- 0a3dc07e microvm: add bind-once read-only image slots
- 4eb09487 microvm: fold image slots into ABI 2
- 67b641d8 microvm: harden host-control framing, logging, and cleanup

A machine that does not declare image slots keeps its device topology,
and every machine and snapshot keeps microVM ABI 2, so
SOURCE-MANIFEST.json and the benchmark history dimensions are unchanged.

- Image slot 0 shares the second virtio-fs slot's MMIO window and
  shared-status word, and slot 1 its IRQ 13, so a slot-declaring launch
  attaches at most one live share.
- The restore packet carries an image-slot target in header byte 7,
  flagged by bit 4. nvx-time validates and reports it, never finishes
  such a restore itself, and nvx-snapshot activates the requested prefix
  before acknowledging the gated restore.
- The shared-status kernel patch maps slots 1 to 3 to their status words.
- Managed sandboxes can declare slots at provision time (configuration
  format 6), boot without a distro layer, and query or bind slots
  through the authenticated host-control channel. Only a slot-declaring
  sandbox records a host-control endpoint, and stop and deprovision
  remove its socket.
- The image-slot scenario and boot-cost benchmark hand OpenVMM its
  capability through a prepared pipe, as the other launchers do (#440).
- The Azure Linux initramfs installs nvx-image-slot.
- Add an image-slot correctness scenario and a boot-cost benchmark.

nvx.py verify passes with this pin.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
esaurez added a commit to microsoft/nvx that referenced this pull request Oct 9, 2026
Pin OpenVMM 67b641d8 (nanvix/openvmm#116), which adds four optional
bind-once read-only image-slot transports to the microVM as part of
ABI 2. nanvix/openvmm#116 is based on OpenVMM main f4566bab, the current
dev pin, so this promotion adds exactly its four commits:

- f5c8dab0 virtio_blk: add bind-once image slots
- 0a3dc07e microvm: add bind-once read-only image slots
- 4eb09487 microvm: fold image slots into ABI 2
- 67b641d8 microvm: harden host-control framing, logging, and cleanup

A machine that does not declare image slots keeps its device topology,
and every machine and snapshot keeps microVM ABI 2, so
SOURCE-MANIFEST.json and the benchmark history dimensions are unchanged.

- Image slot 0 shares the second virtio-fs slot's MMIO window and
  shared-status word, and slot 1 its IRQ 13, so a slot-declaring launch
  attaches at most one live share.
- The restore packet carries an image-slot target in header byte 7,
  flagged by bit 4. nvx-time validates and reports it, never finishes
  such a restore itself, and nvx-snapshot activates the requested prefix
  before acknowledging the gated restore.
- The shared-status kernel patch maps slots 1 to 3 to their status words.
- Managed sandboxes can declare slots at provision time (configuration
  format 6), boot without a distro layer, and query or bind slots
  through the authenticated host-control channel. Only a slot-declaring
  sandbox records a host-control endpoint, and stop and deprovision
  remove its socket.
- The image-slot scenario and boot-cost benchmark hand OpenVMM its
  capability through a prepared pipe, as the other launchers do (#440).
- The Azure Linux initramfs installs nvx-image-slot.
- Add an image-slot correctness scenario, a boot-cost benchmark, and
  socket-backed host-control client tests.

nvx.py verify passes with this pin.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
esaurez added a commit to microsoft/nvx that referenced this pull request Oct 9, 2026
Pin OpenVMM 67b641d8 (nanvix/openvmm#116), which adds four optional
bind-once read-only image-slot transports to the microVM as part of
ABI 2. nanvix/openvmm#116 is based on OpenVMM main f4566bab, the current
dev pin, so this promotion adds exactly its four commits:

- f5c8dab0 virtio_blk: add bind-once image slots
- 0a3dc07e microvm: add bind-once read-only image slots
- 4eb09487 microvm: fold image slots into ABI 2
- 67b641d8 microvm: harden host-control framing, logging, and cleanup

A machine that does not declare image slots keeps its device topology,
and every machine and snapshot keeps microVM ABI 2, so
SOURCE-MANIFEST.json and the benchmark history dimensions are unchanged.

- Image slot 0 shares the second virtio-fs slot's MMIO window and
  shared-status word, and slot 1 its IRQ 13, so a slot-declaring launch
  attaches at most one live share.
- The restore packet carries an image-slot target in header byte 7,
  flagged by bit 4. nvx-time validates and reports it, never finishes
  such a restore itself, and nvx-snapshot activates the requested prefix
  before acknowledging the gated restore.
- The shared-status kernel patch maps slots 1 to 3 to their status words.
- Managed sandboxes can declare slots at provision time (configuration
  format 6), boot without a distro layer, and query or bind slots
  through the authenticated host-control channel. Only a slot-declaring
  sandbox records a host-control endpoint, and stop and deprovision
  remove its socket.
- The image-slot scenario and boot-cost benchmark hand OpenVMM its
  capability through a prepared pipe, as the other launchers do (#440).
- The Azure Linux initramfs installs nvx-image-slot.
- Add an image-slot correctness scenario, a boot-cost benchmark, and
  socket-backed host-control client tests.

nvx.py verify passes with this pin.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@esaurez

esaurez commented Oct 20, 2026

Copy link
Copy Markdown
Author

Closing together with microsoft/nvx#406: the image-backed runtime plan in microsoft/nvx#535 leaves multiple image slots out of scope. The branch stays, so existing pins remain fetchable.

@esaurez esaurez closed this Oct 20, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants