Skip to content

tests: start clones with exports in VMs - #956

Closed
Enrique Saurez (esaurez) wants to merge 10 commits into
esaurez/846-vm-tests-base-snapshot-20261028from
esaurez/846-vm-tests-20261025
Closed

Enrique Saurez (esaurez) wants to merge 10 commits into
esaurez/846-vm-tests-base-snapshot-20261028from
esaurez/846-vm-tests-20261025

Conversation

@esaurez

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

Copy link
Copy Markdown
Contributor

Review base: this PR temporarily targets esaurez/846-vm-tests-base-snapshot-20261028, a frozen copy of the work it builds on, so that its diff shows only its own commits while the pull requests below it are in review. That work is #846's PR 1 (#955) merged with #845's VM scenarios (#961). This pull request does not merge on its own: at the final restack, its commits fold into #846's PR 1 (#955), and it then closes.

Part of #846 (PR 1's VM scenarios) and of #845 (its export scenarios).

Commits

  • runtime-adapter: let the restore sequence send a clone's mapping table: what the merge of nvx-rs: exports, MAPS and network templates for clones #846's PR 1 with nvx-rs: activate a clone's root for GPT images and either upper, with its export, network settings and workload view #845's VM scenarios needs. NVX's restore sequence now sends a clone's mapping table itself, so clone_order(), the one place that makes the adapter's CloneOrder for a clone's start and a reattach to it, builds CloneOrder { build }. A clone's request names no export yet, CloneRequest.export: None, and the harness's Early framing, which ends before the clone is ready, gains a map() that it never reaches.
  • runtime-adapter: let a clone's start share host paths with its root: runtime-adapter-host start takes a launch's shares, --share HOST:TARGET[:ro], --deny, --reserved, and --share-root, with the same rules: --deny, --reserved, and --share-root need a --share. clone::Request carries them, and Request::export plans their export as a launch plans it, with filesystem::plan for the container root, so a start refuses shares that it cannot export, with status 1, before it reads the template store, needs NVX's artifacts, or creates the sandbox's store. Request::clone_request takes the planned export into NVX's CloneRequest, and mapped= prints the number of mappings that the clone's activation record announces.
  • runtime-adapter: start clones with exports, in VMs: the scenarios below. The harness reads a clone's activation record from its sandbox, and, on Linux, the arguments of its VMM.
  • nvx-rs: resolve the template store's path in a clone's export check: a fix of PR 1's export check. ClonePlan::new refuses an export that overlaps the template store, whose leased payloads nothing may change. It compared the paths that the export's plan resolved with the store's directory as the store names it, so another spelling of the store's path hid an overlap: a link; on Windows, another letter case, a short name, or the verbatim prefix; and on Unix, a .. component, which an absolute path keeps. The plan now resolves the store's directory with filesystem::canonicalize, as filesystem::plan resolves the export's paths. The host still reaches the store, and OpenVMM the snapshots in it, by the path that the store names, so the plan also resolves the directories in which the host looks up the names of that path, links followed and .. included, and refuses an export that lets the guest write in one of them, where the guest could replace a link of the path and turn the host's later operations on the store elsewhere, or make a directory of the path unsearchable and keep the host from the store. It does both only for a clone with an export, so a clone without one reads nothing more. A store whose path cannot be resolved fails the plan as the host's failure: HostFailure, or ResourceExhaustion when the error proves it. The plan also refuses an export whose directory is, or holds, the directory of the clone's sandbox, in which the host writes the clone's records by their paths, which the guest could otherwise change, or turn elsewhere, into a leased snapshot, say; a directory inside the sandbox's, among the caller's files, may still be exported. These checks compare paths, so an alias of the store that an administrator configures, such as a bind mount of it, is outside them, as the directories above the store are, and they cannot see what changes after them, since OpenVMM resolves an export's path again at each launch and restore: the description of the export, in ClonePlan's documentation and in the README, says that whoever names an export must not name such an alias, nor a directory that holds one, nor one inside a directory that a writable export of another sandbox or clone holds, so that no guest may change the directories above an export's directory, as for the clone's block devices.

Fix-ups

  • fixup! runtime-adapter: start clones with exports, in VMs: Export::write, the fixture of the scenarios' exports, now lets anyone create entries in each directory of an export, its root included, so that only a mapping's access refuses what an execution writes there. A Linux host shows the guest the owner and the mode of each host object, which the guest's kernel checks, and an execution as user ID 0 holds no capability that overrides the check. Where the tests run unprivileged, the export belongs to the test's user, so the guest refused the execution's writes even through the writable mappings. A Windows host shows the guest user ID 0 as each object's owner.
  • amend! nvx-rs: resolve the template store's path in a clone's export check: the check of the directories through which the store's path leads, above, which the commit's resolution of the store's path alone had dropped: an export of a directory that holds a link of the store's path, which the store's resolved path does not pass, no longer overlaps the store, though a guest that writes in it could replace the link. path_directories follows each link of the path, and of its targets, through at most 64 links, and the export's check compares them with the paths in which the guest may write: the whole directory of a writable export, or the paths to which the plan limits its writes. A test names its store through two links, one to the other's directory, and refuses a writable export of the directory of either link, but not a read-only one, nor one whose writes the plan limits to a directory beside the link.
  • A second amend! nvx-rs: resolve the template store's path in a clone's export check: path_directories skipped .., but Unix looks .. up in the directory before it, which the host must be able to search, so with a store named /srv/traverse/../private/templates, a writable export of /srv/traverse let the guest make that directory unsearchable, and keep an unprivileged host from the store. The directory in which the host looks up . or .. now joins the others, and the refusal says that the guest could turn the store's path elsewhere, or keep the host from following it. A test names its store through a directory and its ..: Unix refuses a writable export of the directory, but not a read-only one, nor one whose writes the plan limits to a directory in it, and Windows, which resolves .. from the path's text, accepts all three.
  • A third amend! nvx-rs: resolve the template store's path in a clone's export check, documentation only: the export's checks compare paths, so an alias of the store that an administrator configures is outside them, as the directories above the store are: a bind mount or a mount point of the store, or of a directory above or inside it, a loopback share, or a hard link of a payload. ClonePlan's description of the export, DirectoryExport::check_outside, and the README's nvx::host::template row now say that whoever names an export must not name such an alias, nor a directory that holds one. ClonePlan's description of the export also states the check of the store's path, which it had left out.
  • A fourth amend! nvx-rs: resolve the template store's path in a clone's export check, documentation only: OpenVMM resolves an export's path again at each launch and restore, after the plan's checks and ExportPlan::verify, so a guest that may write the directory that holds an export's directory, through a writable export of another sandbox or clone, could replace that directory with a link as OpenVMM resolves it, and turn the export elsewhere, to a leased payload, say. ClonePlan's description of the export, DirectoryExport::check_outside, and the README's nvx::host::template row now say that the directories above an export's directory must be ones that no guest may change, as those above the clone's block devices must, which the store's "Block devices by path" describes: whoever names an export must not name one inside a directory that a writable export of another sandbox or clone holds.
  • A fifth amend! nvx-rs: resolve the template store's path in a clone's export check: the export check also refuses an export whose directory is, or holds, the directory of the clone's sandbox. The host writes the clone's records there by their paths, such as its runtime record when a launch commits and its failure records, so a guest that could reach the directory through a writable export could change them, or replace the directory with a link to a leased snapshot and turn the host's writes into it. The lease names the sandbox by its path with its links resolved, so the directories through which that path leads are the ones above it, which only such an export could let the guest change, and the check reads nothing more. A directory inside the sandbox's, among the caller's files, may still be exported. A test refuses an export of the sandbox's directory, writable, and of the directory that holds it, read-only, and accepts a writable export of a directory inside the sandbox's.

VM scenarios (vm_clone), on MSHV and WHP

  • A clone over a scratch image that shares one directory: the directory itself, read-only, through the root bind's empty source, a read-write directory inside it, at its target there, a read-only file, and a read-write directory, with a file hidden. It starts with four mappings, its record names the export, a directory, by the identity of its plan, with four mappings, and on Linux its VMM has one --mount, of the directory, read-write. The guest mounted the export once, read-write, nosuid, and nodev, and the mappings below the root in the table's order, each with its access, showing what the host holds but for the hidden file. An execution finds the same, and the writable mappings alone take its writes, which the host's directory holds before and after the stop.
  • Four clones of one generation at once: one with a read-write export and a generated machine ID, two over the same read-only export, and one without an export, which has no export in its record, no mapping, and, on Linux, no --mount, so its slot stays dormant. Each sees only its own directory, and the records of one export name it by the same identity. An export is mounted read-write even where its mapping is read-only, the mount table lists the bind of the generated machine ID beside the mapping, and the helper of the entries probe, which writes the root's machine ID, finds that file read-only.
  • Starts that share two directories, or a file, are refused before any VMM starts, with status 8 and the record of a failure of the request, at the plan, by the evidence of invalid mappings.
  • A clone whose second mapping puts a directory onto a file of the image ends at its Ready, before its sandbox starts, with status 8, the guest's report that the table failed after its first mapping, and the record of a failure of the start, by the evidence of the protocol: nothing quarantined, the lease released, and the host's directory unchanged.

Depends on an OpenVMM fix

Three of the scenarios send a mapping table, so they need the OpenVMM fix that #904 tracks: a_clone_maps_its_export_beneath_its_root_before_its_sandbox_starts, clones_at_once_see_only_their_own_exports, and a_clone_whose_mapping_fails_ends_before_its_sandbox_starts. With the pinned OpenVMM, their mapping requests time out; with the fix, they pass. The fourth, a_start_refuses_an_export_that_a_dormant_filesystem_slot_cannot_bind_before_any_vmm_starts, starts no VMM, and passes with either.

Tests without a hypervisor

  • clone_start: the start's new usage errors (--deny, --reserved, and --share-root without a share, and malformed shares); shares that it cannot export, refused with status 1 before the template store; and the export that NVX's request carries: none without shares, and with them one directory, the root of a read-only share that holds a read-write one, mapped from an empty source and from its path.
  • nvx-rs: the test of an export that overlaps the store names the store in another spelling of its directory, through .. on Unix and with the verbatim prefix on Windows; another names it through two links, symbolic links on Unix and junctions on Windows, and refuses a writable export of the directory of either link, but not a read-only one, nor one whose writes the plan limits to a directory beside the link; a third names it through a directory and its .., whose writable export Unix refuses and Windows accepts; a fourth refuses an export of the sandbox's directory, and of the directory that holds it, but not of a directory inside it.

Validation

At this head, with the guest agent and the initramfs built from it:

  • Windows: the nvx-rs checks, the test adapter's checks, the aci_edge_sandboxes checks, Clippy of the nvx-rs guest code for x86_64-unknown-linux-musl, scripts/nvx.py verify, and the Markdown link check.
  • Linux: the 31 steps of check-nvx-rs and of the dependent crates' checks, and the root-only tests, 12 of 12.
  • VMs, with the pinned OpenVMM, on MSHV and WHP: vm_clone 16 of 19 (all but the three scenarios above), vm_layered 43, vm_restart 3, vm_steps 3, vm_template 9, host_session 25, layered_launch 20, and clone_start 6. With the OpenVMM fix, the four export scenarios pass on both.

For the fix-up: the test adapter's checks on Windows, and the 29 steps of the Linux checks. On MSHV with the OpenVMM fix, the export scenarios pass, and with an export owned by another user than root, as on an unprivileged host, an execution as user ID 0 had its writes through the writable mappings refused before the fix-up, and makes them after it.

With OpenVMM e68871cc, which has the fix, at a head that holds this pull request and #846's PR 2 (#958):

  • CI: every job of the workflow passed, the VM tests on KVM, MSHV, and WHP included, whose unprivileged Linux runners now pass the export scenarios too. vm_clone passed 24 of 24 on each.
  • MSHV and WHP: vm_clone 24 of 24, vm_layered 43, vm_restart 3, vm_steps 3, vm_template 9, host_session 25, layered_launch 20, and clone_start 8.

Enrique Saurez and others added 4 commits October 25, 2026 20:30
nvx's restore sequence now sends a clone's mapping table itself, through
CloneControl::map, once the clone is ready, and Start only once the guest
applied every mapping. The test adapter's CloneOrder therefore takes only
the guest agent's build: clone_order(), the one place that makes it, for a
clone's start and a reattach to it, builds CloneOrder { build }.

A clone's request names no export yet, CloneRequest.export: None, so a
clone still maps nothing. The harness's Early framing, which ends before
the clone is ready, gains a map() that it never reaches.

Part of #846.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 34e6ffe3-3ebf-42db-bfca-e0ecfade6873
`runtime-adapter-host start` takes the shares of a launch, `--share
HOST:TARGET[:ro]`, `--deny`, `--reserved`, and `--share-root`, with the
same rules: `--deny`, `--reserved`, and `--share-root` need a `--share`.
`clone::Request` carries them as `layered::Shares`, and `Request::export`
plans their export as a launch plans it, with NVX's `filesystem::plan`
for the container root, so the start refuses shares that it cannot
export, with status 1, before it reads the template store, needs NVX's
artifacts, or creates the sandbox's store.

`Request::clone_request` takes the planned export into NVX's
`CloneRequest`, whose `ClonePlan` takes only an export of one directory,
which the clone's restore binds to the template's dormant filesystem
slot, and refuses others, as a failure of the request, before any VMM
starts. The restore sequence sends the export's mapping table through
the adapter's clone order once the clone is ready, and `mapped` now
prints the number of mappings that the clone's activation record
announces, which the guest applied before its sandbox started.

Tests without a hypervisor cover the new usage errors, the refusals
before the template store, and the export that NVX's request carries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 34e6ffe3-3ebf-42db-bfca-e0ecfade6873
The VM tests start clones that share host directories with their roots,
through `runtime-adapter-host start` and its shares, which NVX exports
as one directory, bound to the template's dormant filesystem slot, and
whose mapping table the restore sequence sends once the clone is ready.

In vm_clone:

- A clone over a scratch image shares one host directory: the directory
  itself, read-only, a read-write directory inside it, at its target
  there, a read-only file, and a read-write directory, with a file
  hidden. It starts with four mappings, and its activation record names
  the export, a directory, by the identity of its plan, with four
  mappings. On Linux, its VMM has one `--mount`, of the directory,
  read-write. The guest mounted the export once, read-write, nosuid and
  nodev, and the mappings below the root, in the table's order, each
  with its access, showing what the host holds but for the hidden file.
  An execution finds the same, and the writable mappings alone take its
  writes, which the host's directory holds, before and after the stop.
- Four clones of one generation at once: one with a read-write export
  of its own and a generated machine ID, two over the same read-only
  export, and one without an export. Each record names its export by
  the identity of its plan, the same for the clones of one export, and
  the clone without one has none, no mapping, and on Linux no `--mount`,
  which leaves the slot dormant. Each export is mounted read-write, even
  where its mapping is read-only; the mount table lists the bind of the
  generated machine ID beside the mapping; and the helper of the
  `entries` probe, which writes the root's machine ID, finds that file
  read-only.
- A start refuses two directories, or a file, before any VMM starts,
  with status 8 and the record of a failure of the request, at the plan,
  by the evidence of invalid mappings.
- A clone whose second mapping puts a directory onto a file of the
  image ends at its Ready, before its sandbox starts, with status 8, the
  guest's report that the table failed after its first mapping, and the
  record of a failure of the start, by the evidence of the protocol:
  nothing quarantined, the lease released, and the host's directory
  unchanged.

The harness reads a clone's activation record from its sandbox, and on
Linux the arguments of its VMM.

Part of #846
Part of #845

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 34e6ffe3-3ebf-42db-bfca-e0ecfade6873
ClonePlan::new refuses an export that overlaps the template store,
whose leased payloads nothing may change. It compared the paths that
the export's plan resolved with the store's directory as the store
names it, so another spelling of the store's path hid an overlap: a
link; on Windows, another letter case, a short name, or the verbatim
prefix; and on Unix, a `..` component, which an absolute path keeps.

The plan now resolves the store's directory with
filesystem::canonicalize, as filesystem::plan resolves the export's
paths, before it compares them. It does so only for a clone with an
export, so a clone without one reads nothing more. A store whose
directory cannot be resolved fails the plan as the host's failure.

The test of the overlap names its store in another spelling of its
directory: through `..` on Unix, and with the verbatim prefix on
Windows.

Part of #846

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 34e6ffe3-3ebf-42db-bfca-e0ecfade6873

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The final restack depends on the external OpenVMM fix in #904 and still requires confirmation of the corrected pin and passing export VM scenarios on KVM, MSHV, and WHP.

No open issues 🎉

What changed in this PR

Adds clone-export support to the runtime adapter and VM coverage for #845 and #846, intended to fold into #955. It also closes a template-store overlap-check gap caused by alternate path spellings.

Changes:

  • Lets clone starts accept host shares and pass their planned export to NVX’s restore sequence.
  • Adds tests for mapping access, clone isolation, invalid exports, and cleanup after mapping failures.
  • Canonicalizes the template-store path before checking export overlap and updates documentation.

The three VM scenarios that send mapping tables depend on the OpenVMM fix tracked by #904.

File Description
tests/​runtime-adapter/​tests/​vm_clone.rs Adds four clone-export VM scenarios and inspection helpers.
tests/​runtime-adapter/​tests/​clone_start.rs Tests share validation and export propagation.
tests/​runtime-adapter/​src/​clone.rs Adds shares and export planning to clone requests.
tests/​runtime-adapter/​src/​bin/​runtime-adapter-host.rs Accepts clone share options and reports mapping counts.
tests/​runtime-adapter/​README.md Documents clone shares, mapping behavior, and scenarios.
nvx-rs/​src/​host/​template/​clone/​tests.rs Tests overlap rejection with alternate store paths.
nvx-rs/​src/​host/​template/​clone/​export.rs Clarifies the resolved-path requirement for overlap checks.
nvx-rs/​src/​host/​template/​clone.rs Resolves the store path before checking export overlap.
nvx-rs/​README.md Updates the documented overlap-check behavior.

Review effort: Balanced

Let anyone create entries in each directory of a scenario's export, its root included, so that only a mapping's access refuses what a clone's execution writes there.

A Linux host shows the guest each host object's owner and mode, which the guest's kernel checks. On an unprivileged host, the export belongs to the test's user, not to user ID 0, as which the executions run without the capabilities that override the check, so the guest refused their writes through the writable mappings. A Windows host shows the guest user ID 0 as each object's owner.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 34e6ffe3-3ebf-42db-bfca-e0ecfade6873

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Three new VM scenarios fail against the frozen OpenVMM pin, requiring maintainer confirmation that the final restack includes #904’s restore fix and that export mapping and teardown pass on KVM, MSHV, and WHP.

No open issues 🎉

Review effort: Balanced

…check

nvx-rs: resolve the template store's path in a clone's export check

ClonePlan::new refuses an export that overlaps the template store,
whose leased payloads nothing may change. It compared the paths that
the export's plan resolved with the store's directory as the store
names it, so another spelling of the store's path hid an overlap: a
link; on Windows, another letter case, a short name, or the verbatim
prefix; and on Unix, a `..` component, which an absolute path keeps.

The plan now resolves the store's directory with
filesystem::canonicalize, as filesystem::plan resolves the export's
paths, before it compares them. The host still reaches the store, and
OpenVMM the snapshots in it, by the path that the store names, so the
plan also resolves the directories through which that path leads, links
included, and refuses an export that lets the guest write in one of
them: the guest could otherwise replace a link of the path, and turn
the host's later operations on the store elsewhere. It does both only
for a clone with an export, so a clone without one reads nothing more.
A store whose path cannot be resolved fails the plan as the host's
failure.

The test of the overlap names its store in another spelling of its
directory: through `..` on Unix, and with the verbatim prefix on
Windows. Another names it through two links, one to the other's
directory: it refuses a writable export of the directory of either
link, but not a read-only one, nor one whose writes the plan limits to
a directory beside the link.

Part of #846

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 34e6ffe3-3ebf-42db-bfca-e0ecfade6873

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Security-sensitive symlink and junction traversal needs platform-specific maintainer review of path resolution and rename-attack protections, with runtime validation unavailable in this source-only review.

No open issues 🎉

Review effort: Balanced

Enrique Saurez (esaurez) pushed a commit that referenced this pull request Oct 28, 2026
This branch exists only to run CI. It is never a pull request's head,
and this commit never goes onto a pull request's branch.

It is #958's head, 4a614d9, which holds #955, #956, and #958, with
the fixes that dev carries for the failures that their review bases,
pinned to OpenVMM 79a501b6, show in CI, and that the pull requests do
not change:

- #885's test-only 3fbaf8a, cherry-picked before this commit. On
  GitHub's hosted Windows runners an open with backup semantics
  bypasses the test's access list.
- This commit, which moves the openvmm gitlink 79a501b6 -> e68871cc,
  the virtio-fs fix of #904 that dev pins since #918, so that a clone's
  export serves requests after a gated restore. The platform tier's
  allowlist there admits maxcpus=, so the copy of it in
  nvx-rs/tests/vectors/openvmm, which the CLI jobs compare with the
  submodule, takes that line too.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 34e6ffe3-3ebf-42db-bfca-e0ecfade6873
…check

nvx-rs: resolve the template store's path in a clone's export check

ClonePlan::new refuses an export that overlaps the template store,
whose leased payloads nothing may change. It compared the paths that
the export's plan resolved with the store's directory as the store
names it, so another spelling of the store's path hid an overlap: a
link; on Windows, another letter case, a short name, or the verbatim
prefix; and on Unix, a `..` component, which an absolute path keeps.

The plan now resolves the store's directory with
filesystem::canonicalize, as filesystem::plan resolves the export's
paths, before it compares them. The host still reaches the store, and
OpenVMM the snapshots in it, by the path that the store names, so the
plan also resolves the directories in which the host looks up the
names of that path, links followed and `..` included, and refuses an
export that lets the guest write in one of them: the guest could
otherwise replace a link of the path, and turn the host's later
operations on the store elsewhere, or make a directory of the path
unsearchable, and keep the host from the store. It does both only for
a clone with an export, so a clone without one reads nothing more. A
store whose path cannot be resolved fails the plan as the host's
failure.

The test of the overlap names its store in another spelling of its
directory: through `..` on Unix, and with the verbatim prefix on
Windows. Another names it through two links, one to the other's
directory: it refuses a writable export of the directory of either
link, but not a read-only one, nor one whose writes the plan limits to
a directory beside the link. A third names it through a directory and
its `..`, which Unix looks up in that directory: Unix refuses a
writable export of the directory, and Windows, which resolves `..`
from the path's text, accepts it.

Part of #846

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 34e6ffe3-3ebf-42db-bfca-e0ecfade6873

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The new VM scenarios depend on an external OpenVMM fix, and the final restacked integration head still needs confirmation on KVM, MSHV, and WHP because reported validation used a combined stack.

No open issues 🎉

Review effort: Balanced

…check

nvx-rs: resolve the template store's path in a clone's export check

ClonePlan::new refuses an export that overlaps the template store,
whose leased payloads nothing may change. It compared the paths that
the export's plan resolved with the store's directory as the store
names it, so another spelling of the store's path hid an overlap: a
link; on Windows, another letter case, a short name, or the verbatim
prefix; and on Unix, a `..` component, which an absolute path keeps.

The plan now resolves the store's directory with
filesystem::canonicalize, as filesystem::plan resolves the export's
paths, before it compares them. The host still reaches the store, and
OpenVMM the snapshots in it, by the path that the store names, so the
plan also resolves the directories in which the host looks up the
names of that path, links followed and `..` included, and refuses an
export that lets the guest write in one of them: the guest could
otherwise replace a link of the path, and turn the host's later
operations on the store elsewhere, or make a directory of the path
unsearchable, and keep the host from the store. It does both only for
a clone with an export, so a clone without one reads nothing more. A
store whose path cannot be resolved fails the plan as the host's
failure.

These checks compare paths, so an alias of the store that an
administrator configures, such as a bind mount of it, is outside them,
as the directories above the store are: the documentation of the
export, in ClonePlan's and in the README, now says that whoever names
an export must not name such an alias, nor a directory that holds one.

The test of the overlap names its store in another spelling of its
directory: through `..` on Unix, and with the verbatim prefix on
Windows. Another names it through two links, one to the other's
directory: it refuses a writable export of the directory of either
link, but not a read-only one, nor one whose writes the plan limits to
a directory beside the link. A third names it through a directory and
its `..`, which Unix looks up in that directory: Unix refuses a
writable export of the directory, and Windows, which resolves `..`
from the path's text, accepts it.

Part of #846

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 34e6ffe3-3ebf-42db-bfca-e0ecfade6873

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Custom symlink/junction traversal and path-based exclusions create a security-sensitive isolation boundary requiring human review of Unix and Windows path-resolution and permission assumptions.

No open issues 🎉

Review effort: Balanced

Enrique Saurez (esaurez) pushed a commit that referenced this pull request Oct 28, 2026
This branch exists only to run CI. It is never a pull request's head,
and this commit never goes onto a pull request's branch.

It is #958's head, 4a614d9, which holds #955, #956, and #958, with
the fixes that dev carries for the failures that their review bases,
pinned to OpenVMM 79a501b6, show in CI, and that the pull requests do
not change:

- #885's test-only 3fbaf8a, cherry-picked before this commit. On
  GitHub's hosted Windows runners an open with backup semantics
  bypasses the test's access list.
- This commit, which moves the openvmm gitlink 79a501b6 -> e68871cc,
  the virtio-fs fix of #904 that dev pins since #918, so that a clone's
  export serves requests after a gated restore. The platform tier's
  allowlist there admits maxcpus=, so the copy of it in
  nvx-rs/tests/vectors/openvmm, which the CLI jobs compare with the
  submodule, takes that line too.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 34e6ffe3-3ebf-42db-bfca-e0ecfade6873
…check

nvx-rs: resolve the template store's path in a clone's export check

ClonePlan::new refuses an export that overlaps the template store,
whose leased payloads nothing may change. It compared the paths that
the export's plan resolved with the store's directory as the store
names it, so another spelling of the store's path hid an overlap: a
link; on Windows, another letter case, a short name, or the verbatim
prefix; and on Unix, a `..` component, which an absolute path keeps.

The plan now resolves the store's directory with
filesystem::canonicalize, as filesystem::plan resolves the export's
paths, before it compares them. The host still reaches the store, and
OpenVMM the snapshots in it, by the path that the store names, so the
plan also resolves the directories in which the host looks up the
names of that path, links followed and `..` included, and refuses an
export that lets the guest write in one of them: the guest could
otherwise replace a link of the path, and turn the host's later
operations on the store elsewhere, or make a directory of the path
unsearchable, and keep the host from the store. It does both only for
a clone with an export, so a clone without one reads nothing more. A
store whose path cannot be resolved fails the plan as the host's
failure.

These checks compare paths, so an alias of the store that an
administrator configures, such as a bind mount of it, is outside them,
as the directories above the store are, and they cannot see what
changes after them: OpenVMM resolves an export's path again at each
launch and restore. The documentation of the export, in ClonePlan's
and in the README, now says that whoever names an export must not name
such an alias, nor a directory that holds one, nor one inside a
directory that a writable export of another sandbox or clone holds:
the directories above an export's directory must be ones that no guest
may change, as those above the clone's block devices must.

The test of the overlap names its store in another spelling of its
directory: through `..` on Unix, and with the verbatim prefix on
Windows. Another names it through two links, one to the other's
directory: it refuses a writable export of the directory of either
link, but not a read-only one, nor one whose writes the plan limits to
a directory beside the link. A third names it through a directory and
its `..`, which Unix looks up in that directory: Unix refuses a
writable export of the directory, and Windows, which resolves `..`
from the path's text, accepts it.

Part of #846

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 34e6ffe3-3ebf-42db-bfca-e0ecfade6873
Enrique Saurez (esaurez) pushed a commit that referenced this pull request Oct 28, 2026
This branch exists only to run CI. It is never a pull request's head,
and this commit never goes onto a pull request's branch.

It is #958's head, 4a614d9, which holds #955, #956, and #958, with
the fixes that dev carries for the failures that their review bases,
pinned to OpenVMM 79a501b6, show in CI, and that the pull requests do
not change:

- #885's test-only 3fbaf8a, cherry-picked before this commit. On
  GitHub's hosted Windows runners an open with backup semantics
  bypasses the test's access list.
- This commit, which moves the openvmm gitlink 79a501b6 -> e68871cc,
  the virtio-fs fix of #904 that dev pins since #918, so that a clone's
  export serves requests after a gated restore. The platform tier's
  allowlist there admits maxcpus=, so the copy of it in
  nvx-rs/tests/vectors/openvmm, which the CLI jobs compare with the
  submodule, takes that line too.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 34e6ffe3-3ebf-42db-bfca-e0ecfade6873
@esaurez
Enrique Saurez (esaurez) requested a balanced review from Copilot October 28, 2026 22:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The cross-platform filesystem isolation checks warrant human security review of symlink/junction traversal, race assumptions, and caller-enforced alias and ancestor protections.

No open issues 🎉

Review effort: Balanced

…check

nvx-rs: resolve the template store's path in a clone's export check

ClonePlan::new refuses an export that overlaps the template store,
whose leased payloads nothing may change. It compared the paths that
the export's plan resolved with the store's directory as the store
names it, so another spelling of the store's path hid an overlap: a
link; on Windows, another letter case, a short name, or the verbatim
prefix; and on Unix, a `..` component, which an absolute path keeps.

The plan now resolves the store's directory with
filesystem::canonicalize, as filesystem::plan resolves the export's
paths, before it compares them. The host still reaches the store, and
OpenVMM the snapshots in it, by the path that the store names, so the
plan also resolves the directories in which the host looks up the
names of that path, links followed and `..` included, and refuses an
export that lets the guest write in one of them: the guest could
otherwise replace a link of the path, and turn the host's later
operations on the store elsewhere, or make a directory of the path
unsearchable, and keep the host from the store. It does both only for
a clone with an export, so a clone without one reads nothing more. A
store whose path cannot be resolved fails the plan as the host's
failure.

The plan also refuses an export whose directory is, or holds, the
directory of the clone's sandbox, in which the host writes the clone's
records by their paths: the guest could otherwise change them, or
replace the directory with a link and turn the host's writes elsewhere,
into a leased snapshot, say. The lease names the sandbox by its path
with its links resolved, so the directories through which that path
leads are the ones above it, which only such an export could let the
guest change. A directory inside the sandbox's, among the caller's
files, may still be exported.

These checks compare paths, so an alias of the store that an
administrator configures, such as a bind mount of it, is outside them,
as the directories above the store are, and they cannot see what
changes after them: OpenVMM resolves an export's path again at each
launch and restore. The documentation of the export, in ClonePlan's
and in the README, now says that whoever names an export must not name
such an alias, nor a directory that holds one, nor one inside a
directory that a writable export of another sandbox or clone holds:
the directories above an export's directory must be ones that no guest
may change, as those above the clone's block devices must.

The test of the overlap names its store in another spelling of its
directory: through `..` on Unix, and with the verbatim prefix on
Windows. Another names it through two links, one to the other's
directory: it refuses a writable export of the directory of either
link, but not a read-only one, nor one whose writes the plan limits to
a directory beside the link. A third names it through a directory and
its `..`, which Unix looks up in that directory: Unix refuses a
writable export of the directory, and Windows, which resolves `..`
from the path's text, accepts it. A fourth refuses an export of the
sandbox's directory, and of the directory that holds it, but not of a
directory inside the sandbox's.

Part of #846

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 34e6ffe3-3ebf-42db-bfca-e0ecfade6873

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The custom path walker’s security boundary, especially Windows junction handling and caller-managed alias/race exclusions, warrants maintainer review before integration.

No open issues 🎉

Review effort: Balanced

Enrique Saurez (esaurez) pushed a commit that referenced this pull request Oct 28, 2026
This branch exists only to run CI. It is never a pull request's head,
and this commit never goes onto a pull request's branch.

It is #958's head, 4a614d9, which holds #955, #956, and #958, with
the fixes that dev carries for the failures that their review bases,
pinned to OpenVMM 79a501b6, show in CI, and that the pull requests do
not change:

- #885's test-only 3fbaf8a, cherry-picked before this commit. On
  GitHub's hosted Windows runners an open with backup semantics
  bypasses the test's access list.
- This commit, which moves the openvmm gitlink 79a501b6 -> e68871cc,
  the virtio-fs fix of #904 that dev pins since #918, so that a clone's
  export serves requests after a gated restore. The platform tier's
  allowlist there admits maxcpus=, so the copy of it in
  nvx-rs/tests/vectors/openvmm, which the CLI jobs compare with the
  submodule, takes that line too.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 34e6ffe3-3ebf-42db-bfca-e0ecfade6873
@esaurez

Copy link
Copy Markdown
Contributor Author

Folded into #955 at the final restack:

  • 3dfbe2ff "runtime-adapter: let the restore sequence send a clone's mapping table" → part of 88417d63 "nvx-rs: export one directory to a clone and map it before its start". At nvx-rs: export one directory to a template's clone, and map it before its start #955's new base, runtime-adapter: start clones of templates #954 builds NVX's CloneRequest, the adapter's CloneOrder with a table, and a CloneControl without map(), so the first commit's test adapter does not compile without this one (the host configuration fails with E0063, a missing export, E0560, CloneOrder without table, and E0046, Early without map), and this one needs what the first commit adds.
  • 0858004c "runtime-adapter: let a clone's start share host paths with its root" → 2741f0f5.
  • 5b274566 "runtime-adapter: start clones with exports, in VMs", with its fix-up 98c5670c → 476c1d04.
  • c7d5a7ad "nvx-rs: resolve the template store's path in a clone's export check", with its amend! commits a4ead4c6, 9d41c41a, 80fae1fd, f175d5e4, and fe6b00a6 → 08d111c7.

#955 now holds these commits, on #960, and this pull request closes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants