Skip to content

[DO NOT REVIEW] openvmm_helpers: compute memory-expansion ranges without overflow - #5003

Merged
Pedro Henrique Penna (ppenna) merged 1 commit into
feature/microvm/devfrom
copilot/nvx-issue-1364-expansion-overflow
Nov 10, 2026
Merged

Pedro Henrique Penna (ppenna) merged 1 commit into
feature/microvm/devfrom
copilot/nvx-issue-1364-expansion-overflow

Conversation

@ppenna

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

Copy link
Copy Markdown
Contributor

Makes a microVM snapshot's canonical memory-expansion ranges compute without overflowing near the top of the guest-physical address space, for microsoft/nvx#1364. A manifest that declares a base RAM of at least 2^64 - 4 GiB below a larger RAM capacity overflowed an addition when OpenVMM built or checked its machine contract, so opening such a crafted snapshot panicked a debug build with "attempt to add with overflow" before the size check of memory.bin could refuse it. No VM has such RAM, but a manifest can declare it.

One commit on b4028c1aa, the head of feature/microvm/dev. microsoft/nvx#1423 pins this head. It is a draft, at the bottom of a stack of three; see Stack.

Compute memory-expansion ranges without overflow (ac3dc27a7)

  • Cause. canonical_memory_expansion_ranges computes the parts of the capacity's RAM layout beyond the base RAM, which memory.bin lacks, when OpenVMM builds a contract and when it checks a contract's shape, as opening a snapshot does. It placed each range at gpa_start + logical_start - file_offset, so for the capacity's high-RAM range, at GPA 4 GiB and memory.bin offset 3 GiB, the addition overflowed whenever the base RAM was at least 2^64 - 4 GiB and below the capacity, although the result, below the range's end, fits. A debug build panicked, and a release build wrapped back to the right address.
  • Change. Subtract first: gpa_start + (logical_start - file_offset). The subtraction cannot underflow, since logical_start is at least file_offset, and the sum is below the range's end, which canonical_microvm_memory_ranges has checked fits in a u64. Every range that the old expression computed without overflowing is unchanged.
  • Tests. memory_expansion_near_gpa_limit_does_not_overflow requires a base RAM of 2^64 - 4 GiB with a capacity of 2^64 - 2 GiB to expand by one 2 GiB range at GPA 2^64 - 3 GiB, the values that NVX's memory-layout design (doc/design/2.3-memory-layout.md) gives, the shape check to accept the manifest whose contract records it, and a restore to the capacity to select it. Without the fix, it panics with "attempt to add with overflow" at microvm.rs:913:24, on Windows and Linux. A contract-builder test would need a full command line and reaches the same helper, so there is none.

Validation

At this head, ac3dc27a7, with Rust 1.97.1. It is the commit that NVX's phase-1 validation tested, 78085e3c6 on 154548a0e, rebased onto b4028c1aa; git range-diff shows the patch identical.

  • Windows: cargo nextest run --profile agent -p openvmm_helpers, 127 passed; cargo clippy --all-targets -p openvmm_helpers and cargo doc --no-deps -p openvmm_helpers, without warnings.
  • Linux (NVX's KVM test VM, Azure Linux 3.0, glibc): the same three commands, 133 passed, without warnings.
  • cargo xtask fmt at the head of the stack, which holds this commit's file as it is here, clean.

At 78085e3c6:

  • The new test panicked without the fix on Windows and Linux, and passed with it on Windows and on NVX's KVM, MSHV, and WHP test VMs; openvmm_helpers passed, 118 tests on Windows and 124 on Linux; cargo clippy, cargo doc, and cargo xtask fmt were clean.
  • A scratch test, not committed, that opens a snapshot whose manifest declares 2^64 - 4 GiB of RAM and a 2^64 - 2 GiB capacity panicked in OpenedSnapshot::open on Windows without the fix, and with it failed with "memory.bin size (1024 bytes) doesn't match manifest (18446744069414584320 bytes)".
  • NVX's restore-memory scenario passed on its KVM, MSHV, and WHP test VMs with OpenVMM built from it, and NVX's runtime-adapter suite passed, 158 of 158, on KVM.

Stack

Three drafts, one for each of three microsoft/nvx issues, each based on the branch of the one before it:

  1. This pull request, for openvmm: memory-expansion range math overflows near the top of the GPA space nvx#1364, on feature/microvm/dev.
  2. [DO NOT REVIEW] openvmm_helpers: refuse a FIFO artifact without waiting for a writer #5004, for openvmm: a writerless FIFO snapshot artifact hangs restore before validation nvx#1154, on this branch.
  3. [DO NOT REVIEW] vmotherboard: gate a device's input without closing it to the vCPUs #5005, for openvmm: snapshot input gating closes device mutexes while other vCPUs still access them nvx#1138, on [DO NOT REVIEW] openvmm_helpers: refuse a FIFO artifact without waiting for a writer #5004's branch.

NVX pins each one's head in its own draft, stacked the same way: microsoft/nvx#1423, microsoft/nvx#1424, and microsoft/nvx#1425, in this order, with microsoft/nvx#1427, part 2 of microsoft/nvx#544, which moves no pin, on top.

This one lands first, by fast-forwarding feature/microvm/dev to this head, ac3dc27a738808d38e1148d3caefc5e89111ee32, since this repository's merge button squashes and would leave NVX's pin off the branch. The next one then has its base retargeted to feature/microvm/dev and lands the same way, at its exact head.

A microVM snapshot that declares a RAM capacity records, in its machine
contract, the canonical memory-expansion ranges: the parts of the
capacity's RAM layout beyond its base RAM, which memory.bin lacks.
canonical_memory_expansion_ranges computes them when OpenVMM builds a
contract and when it checks a contract's shape, as opening a snapshot
does. It placed each range at `gpa_start + logical_start - file_offset`,
so for the capacity's high-RAM range, at GPA 4 GiB and memory.bin
offset 3 GiB, the addition overflowed whenever the base RAM was at
least 2^64 - 4 GiB and below the capacity, although the result, below
the range's end, fits. A debug build panicked with "attempt to add with
overflow", and a release build wrapped back to the right address. No
VM has such RAM, but a crafted manifest can declare it, and opening
that snapshot panicked a debug build before the size check of
memory.bin could refuse it (microsoft/nvx#1364).

Subtract first, `gpa_start + (logical_start - file_offset)`. The
subtraction cannot underflow, since logical_start is at least
file_offset, and the sum is below the range's end, which
canonical_microvm_memory_ranges has checked fits in a u64. Every range
that the old expression computed without overflowing is unchanged.

At feature/microvm/dev 154548a, a scratch test that opens a snapshot
whose manifest declares 2^64 - 4 GiB of RAM and a 2^64 - 2 GiB capacity
panicked in OpenedSnapshot::open on Windows; with the fix, the open
fails with "memory.bin size (1024 bytes) doesn't match manifest
(18446744069414584320 bytes)".

Tests: a unit test that base RAM of 2^64 - 4 GiB with a capacity of
2^64 - 2 GiB expands by one 2 GiB range at GPA 2^64 - 3 GiB, that the
shape check accepts the manifest whose contract records it, and that a
restore to the capacity selects it. It panics without the fix on Linux
and Windows and passes with it, as do the other openvmm_helpers tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added the release-nvx Targets a branch under feature/microvm/. label Nov 9, 2026
Pedro Henrique Penna (ppenna) added a commit to microsoft/nvx that referenced this pull request Nov 9, 2026
Pins ac3dc27a738808d38e1148d3caefc5e89111ee32, the head of
microsoft/openvmm#5003, one commit on b4028c1aa, the head of
feature/microvm/dev:

- ac3dc27a7 openvmm_helpers: compute memory-expansion ranges without
  overflow

OpenVMM placed each of a snapshot's canonical memory-expansion ranges by
adding a memory.bin offset to the capacity range's GPA before it
subtracted the range's own offset, so a base RAM of at least
2^64 - 4 GiB overflowed the addition, although the address fits. A
crafted manifest that declares such RAM panicked a debug build of
OpenVMM when it opened the snapshot, in the shape check of the machine
contract, instead of being refused (#1364). The revision subtracts
first. Release builds, which wrapped back to the right address, and
every RAM size that a VM can have compute the same ranges as before.

The vectors, the source excerpts, the contract versions, and the CPU
profiles that nvx-rs copies from OpenVMM match the revision's, as the
openvmm_vectors and openvmm_cpu_profiles comparisons show.

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
…dmap

Maps NVX's new pin, ac3dc27a7, the head of microsoft/openvmm#5003, at
position 236, after position 235, b4028c1aa, the head of
feature/microvm/dev, which microsoft/openvmm#5001 merged. The pin is
105 commits past the mapped tip, and positions 132-236 stay unassigned.

Position 236 changes how the canonical memory-expansion ranges that
PR20's position 099 adds to the machine contract of PR17's position 074
compute their start: it offsets into the capacity's range before it
adds the range's start, so that a base RAM of at least 2^64 - 4 GiB no
longer overflows the sum. It adds a test beside position 099's tests of
the memory capacity. Replaying it on upstream main conflicts in its
only file, since upstream main lacks the microVM snapshot module of
position 074 and the memory expansion of position 099, so it follows
PR17 and PR20 upstream.

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
Pins cba21d109eeb99fbc573c79b2ed9f4754f734b25, the head of
microsoft/openvmm#5004, one commit on ac3dc27a7, the head of
microsoft/openvmm#5003, on which it stacks, which follows b4028c1aa,
the head of feature/microvm/dev:

- cba21d109 openvmm_helpers: refuse a FIFO artifact without waiting for
  a writer

A FIFO without a writer in place of a snapshot's manifest.bin,
state.bin, memory.bin, or paired scratch.img made restore wait forever
before it validated anything (#1154). OpenVMM opened each artifact
without following a final link and only then checked the type of the
file that it opened, and a read-only open of a FIFO waits until a
writer opens it.

The pinned revision opens artifacts with O_NONBLOCK on Unix, so it
refuses such a FIFO at once, with "<artifact> is not a regular file",
before a restore can claim a resume snapshot, and then makes a regular
file's descriptor blocking again. Windows is unchanged: its relative
opens never wait for a file's type. The protocol vectors, the excerpts
and contract versions that nvx-rs copies from OpenVMM, and its CPU
profiles match the revision's.

With 904a33c32, the same patch on d501e5066, on nvx-kvm-test and
nvx-mshv-test (KVM and MSHV virtual machines), a FIFO in place of each
artifact of a workload-start and an instance-checkpoint snapshot made
the OpenVMM of release v0.1.0-dev.a0173e7b7d97, at 6cb6ba87c, wait in
the kernel's wait_for_partner until it was killed 30 s later, while
the fix's glibc and musl builds exited with status 1 and that error
within 0.01 s; none of the 32 restores claimed the instance
checkpoint. On Windows, an AF_UNIX socket, which is a reparse point, in
place of each artifact made that release's openvmm.exe refuse it at
once, so WHP had no such wait. The snapshot-tiers and scratch-snapshot
scenarios passed with it on KVM, MSHV, and WHP.

Tests: OpenVMM's openvmm_helpers tests pass at the pinned revision,
135 of 135 on Linux (glibc) and 127 of 127 on Windows, as they did at
904a33c32, 131 of 131 on Linux, glibc and musl, and 123 of 123 on
Windows, where the two new ones failed without the change. The
openvmm_vectors and openvmm_cpu_profiles comparisons pass.

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, cba21d109, the head of microsoft/openvmm#5004, at
position 237, after position 236, ac3dc27a7, the head of
microsoft/openvmm#5003, on which it stacks, which follows position 235,
b4028c1aa, the head of feature/microvm/dev. The pin is 106 commits past
the mapped tip, and positions 132-237 stay unassigned.

Position 237 changes the artifact openers of the snapshot file system
module that PR11's position 032 adds, with the path-based opener and
the type check, and its position 033 extends with the
directory-relative opener, through which PR12's position 035 restores:
on Unix both now open with O_NONBLOCK, so that the type check refuses
a FIFO without waiting for a writer, and then make a regular file's
descriptor blocking again. It adds tests beside positions 032's and
035's, and extends the restore paragraph that PR12's position 036 adds
to the snapshot format guide. Replaying it on upstream main conflicts
in all three of its files, since upstream main lacks the file system
module of position 032, the directory-relative restore of position 035,
and that paragraph, so it follows PR11, PR12, and PR19 upstream.

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
A guest's snapshot request could crash a multi-vCPU microVM, or lose
the device accesses of its other vCPUs (#1138). The snapshot boundary
gates host input before it stops the vCPUs, so they run while each
device's state unit changes the device's gate, and the unit held the
device's mutex closed for the change, until the end of its wait. A
port or MMIO access that another vCPU made then was dropped, a read
returning all ones; an interrupt from another device was lost; and an
EOI or a PCI configuration access panicked the VM worker with "lock
should not be closed".

Pin 46bfe02ed65697e858f66ce55eb60831b3ac3461, the head of
microsoft/openvmm#5005, one commit on cba21d109, the head of
microsoft/openvmm#5004, on which it stacks, which follows ac3dc27a7,
the head of microsoft/openvmm#5003, and b4028c1aa, the head of
feature/microvm/dev:

- 46bfe02ed vmotherboard: gate a device's input without closing it to
  the vCPUs

The revision splits a device's gate change into a synchronous part,
which the unit makes under the lock that the vCPUs' accesses take, and
a wait that does not borrow the device, which the unit awaits without
it. The virtio transports and the remote device proxy send their
gate's request in the synchronous part, in the order of the guest's
accesses.

Evidence: a microVM guest with 2 and 4 vCPUs requested 2000 snapshots,
each of which gated host input and then failed its preflight, since
its destination existed, while its other vCPUs read the magic value of
the virtio-fs device's MMIO window, and with 4 vCPUs the console's
too, in a loop. With OpenVMM at d6addf8c1, 145 to 2358 of a run's reads
returned all ones, and OpenVMM logged that the device did not respond,
in each of 3 runs per count on nvx-kvm-test (KVM), nvx-mshv-test
(MSHV), and nvx-whp-test (WHP), all virtual machines; with a91d4c7a4,
the same patch on d6addf8c1, no read did. With a91d4c7a4, the default
microVM suite passed on all three, with 1, 2, and 4 vCPUs. At the
pinned revision, the OpenVMM tests of the changed packages and the
virtio devices pass, 545 on Windows and 550 on Linux, as they did at
a91d4c7a4.

NVX needs no coordinated change: the control-session vectors, the
source excerpts, the contract versions, and the CPU profiles that
nvx-rs copies from OpenVMM match the revision's.

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, 46bfe02ed, the head of microsoft/openvmm#5005, at
position 238, after position 237, cba21d109, the head of
microsoft/openvmm#5004, on which it stacks, which follows positions 236,
ac3dc27a7, the head of microsoft/openvmm#5003, and 235, b4028c1aa, the
head of feature/microvm/dev. The pin is 107 commits past the mapped
tip, and positions 132-238 stay unassigned.

Position 238 changes the input gate that PR09's position 018 adds: the
chipset device unit, which changed a device's gate with the device's
mutex closed, now makes the gate's synchronous part under the device's
ordinary lock and awaits the rest without the device, since the vCPUs
run through a gate change. It changes the gate's methods of
ChangeDeviceState, the erased chipset devices, and the remote device
proxy, which position 018 adds, and the virtio transports' forwarding
of the gate, which PR13's position 040 adds, and adds tests of the
unit, both virtio transports, and the proxy. Replaying it on upstream
main conflicts in all eight of the files that it changes, since
upstream main lacks the input gate of position 018 and its forwarding
by position 040, so it follows PR09 and PR13 upstream.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ppenna
Pedro Henrique Penna (ppenna) marked this pull request as ready for review November 9, 2026 23:26
@ppenna
Pedro Henrique Penna (ppenna) removed the request for review from a team November 10, 2026 00:09
@ppenna
Pedro Henrique Penna (ppenna) merged commit ac3dc27 into feature/microvm/dev Nov 10, 2026
144 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-nvx Targets a branch under feature/microvm/.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant