Skip to content

nvx-rs: build a clone's activation record and launch its restore - #949

Merged
Pedro Henrique Penna (ppenna) merged 7 commits into
devfrom
esaurez/844-pr2-activation-restore-launch-20261024
Oct 29, 2026
Merged

Pedro Henrique Penna (ppenna) merged 7 commits into
devfrom
esaurez/844-pr2-activation-restore-launch-20261024

Conversation

@esaurez

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

Copy link
Copy Markdown
Contributor

Stacked on #888; this PR retargets when its parent merges.
Chain: #939 → #940 → #941 → #888 → this PR → #950 → #951, on dev; #879, #881, #885, #886, #877, #932, #884, #887, #933, #880, #936, and #937 have merged.

Part of #844 (PR 2 of 5: the activation record and the restore launch).

What and why

#888's ClonePlan checks a clone's inputs against the generation of the template that it leased. This PR builds the clone's activation record from that plan, keeps it in the clone's sandbox, and launches the clone as OpenVMM's restore of the generation's snapshot, as the launch that the clone's lease reserves. The restore sequence, which reads the VMM's restore-ready event, sends the record in the adapter's Activate, starts the sandbox, and commits the launch, comes in PR 3.

The activation record

ClonePlan::activation() is the record that the clone's guest applies in place of the configuration that its template captured. The plan builds it from the request's workload view, overlay mode, and journal policy, and from the inputs that it checks:

  • a restore section with a fresh transaction ID of 16 random bytes and the lease's TemplateReference;
  • the image, an EROFS layer, with its binding's identity and the UUID that the adapter's plan expects of it;
  • a tmpfs upper, or the scratch image's binding identity with the journal policy;
  • no export;
  • the network of the template's NIC: the policy identity that its key records, the DNS server that the template's guest configured its resolver with, unpublished listeners, and the guest's proxy URL;
  • the template's processors and the memory target.

A binding's identity is the SHA-256 digest of a domain and of the binding's serde form, never of a file's content. TemplateKey::dns_server reads the DNS server from the virtnet_dns= token of the key's effective command line, which OpenVMM adds when the NIC's policy lets the guest reach the gateway's DNS server: once at most, for a template with a NIC, naming one IP address in its canonical spelling. Any other form is a malformed key, which planning a clone refuses. The plan also refuses a GPT image, which a clone's guest does not assemble yet, a network on another guest network than the template's NIC, whose address OpenVMM restores from the snapshot while the launch passes none, and settings that no record holds, before it opens or creates anything. A clone's network must also be under the policy of the template's NIC, which OpenVMM checks as it restores the NIC, refusing another one once the launch began; the key records only OpenVMM's identity of the policy, so the docs state this, and matching a policy to a template is for network templates (#846). Nor may a scratch image be a file of a snapshot: every clone of a generation restores its snapshot in place, while its guest writes its scratch image. Once it claims the image, the plan refuses one in the template store's directory, where the image is now, with every link on its path resolved: the store's captures write their snapshots there and publish them as generations by renaming directories within it, and nothing that the host does moves a file into the store. The image must also have a single name: a hard link could make it a file of a generation's snapshot, of a capture's, or of another store's, that its location does not show. These checks open nothing in the store, however many generations it holds. They resolve the file system's own aliases (links, junctions, directories held open, and other names), but not a mount or a network share that exposes the store's directories elsewhere, so a store's directories must not be exposed that way. A store knows only its own directory, so callers keep scratch images apart from other stores.

The record in the sandbox

The sandbox's store owns two more names: clone-activation.bin, the record, and restore.sock, the socket at which the launch awaits the VMM's restore-ready event on Linux, as long a name as control.sock. A release first ends the lock's reservation of the clone's launch, so that one that fails part of the way leaves no launch of a plan whose files it removed, then removes them with clone-scratch.raw, before the clone binding, whose durable removal makes theirs durable too. A removal of the sandbox does the same, before the sync that makes the binding's absence durable. Both work through one handle of the sandbox's directory, once they checked through it that the directory holds the sandbox's lock, so that no change of the path, of a link in it or of one of its directories, redirects them to another sandbox. The release now does so too, as the removal already did: through that handle, it checks that the sandbox records no VM and no launch, reads the binding, removes the clone's files and the binding, and syncs the directory, and it refuses, removing nothing, a path that names another directory than the lock's. On a Windows file system without POSIX deletion, a file of the clone whose deletion is pending, because a reader holds it, lingers as a pending binding does until the reader closes it, rather than being refused as an entry that is not the store's, and a retried release takes it for removed.

The stores tell their files apart by the identity that a file's seal holds: the device and inode on Linux, and on Windows the volume serial number and the 128-bit file ID of FILE_ID_INFO, which ReFS keeps unique where the 64-bit file index is not. Every check that a path, or an entry of a directory that a store holds open, names the file whose lock the store holds now uses it, as does the comparison of a scratch image's location with the template store's directory. A launch's log claim and the identities of host paths keep the 64-bit identity that they record.

The restore launch

ClonePlan::launch(self, &mut Acquired, openvmm, &RuntimeIdentity) -> Restoring runs the launch that the clone's lease reserves, under the lock that acquired the lease, once the sandbox's clone binding records the lease. It:

  1. writes the record, in its canonical encoding, to clone-activation.bin, from which a caller resends it unchanged after a crash;
  2. listens for the VMM's restore-ready event, at restore.sock on Linux, and at a named pipe of a random name on Windows;
  3. runs supervisor::launch with OpenVMM's restore of the clone's devices: --restore-snapshot with the generation's snapshot in place, which nothing copies, links, or changes, so that clones of one generation restore it at once; --restore-memory with the memory target; --restore-ready-path; the template's backend, CPU profile, and processors; a boot console that discards its output, as cold boots have; and the control console, but no kernel, initramfs, command line, or guest address, which the snapshot fixes.

OpenVMM requires a restore's control socket in the snapshot's parent on Linux, so the lease reserves the clone's control endpoint there: <launch>.sock in the generation's payload, named after the launch that the lease reserves, which the journal and the runtime record name as any launch's endpoint. The launch refuses, before it writes anything, a socket that is not beside the plan's snapshot, a payload whose mode is not exactly 0700, a socket path that, with or without the payload's links resolved, would not fit a Unix socket's address or is not valid UTF-8, as OpenVMM and the host take it, and a control socket path that holds , or =, which end the path in OpenVMM's option listen=<path>. On Windows, the VMM listens at a fresh named pipe, as every launch does. Like every launch of the sandbox's store, the launch names the sandbox's files by their paths under its lock, and OpenVMM opens the files that its arguments name, so the directories above the sandbox's must be ones that only the user, or an administrator, may change, as the sandbox's store and the template store's "Block devices by path" require; the launch's docs say so. For an upper in RAM, the launch also checks, before it writes anything, that the scratch device's file that the plan created is still a regular file of the template's scratch geometry: a later plan under the same acquisition replaces it, and removes it if that plan fails.

The VMM inherits a scratch image's claim: its descriptor on Linux, and on Windows the image's guard, once the launch has closed the claim's handle, which OpenVMM's open of the image, sharing no writing, would refuse.

The VMM removes its socket as it exits. The end of a launch and the clearing of a runtime record remove one that an abrupt exit leaves, and a release removes it and syncs the payload before it ends the lease, so a payload holds the sockets of its clones only while its generation has leases. All three open the payload through a handle of the template store's directory, following no link at the store's directory, at the directory of the generations, or at the payload, refuse a link or a file in their place, and remove the socket through the payload's handle; the release opens the payload before it removes anything, and syncs it through the same handle. Through the handle of the store's directory, they first read the store's identity record, and leave a directory that holds another store, or none, alone: it holds no socket of the lease's. The store's directory and the directory of the generations open with the right to search them alone, so that an owner-only directory that withholds the right to read it, such as one of mode 0300, does not block a stop, a recovery, or a release; the payload opens for reading, so that its handle can sync it. So an acquisition refuses, on Linux, a payload that is not a directory of the current user with mode 0700, as the launch does: a release could not end a lease of such a payload. A retirement removes the payload with whatever a crash left in it. The sandbox's store recognizes a clone's socket by its path in the payload of the generation that the binding's lease names, generations/<generation>/<launch>.sock. Since the sandbox's store builds on nothing of the template module, it makes that check in its own lease module, beside the lease, where the name of the generations' directory, GENERATIONS_NAME, now lives too; the template module re-exports it at its public path, as it re-exports the lease.

Not in this PR

Validation

At this head, 70f3b68, with OpenVMM at d2338ea2, per commit: on Windows, fmt and Clippy with -D warnings for nvx-rs in CI's three configurations (--all-targets --all-features, --lib, and --lib --no-default-features), tests/host_modules.rs, the tests of the platform, template, store, and sandbox modules, and the test adapter's fmt, Clippy, and host tests; and for the x86_64 Linux target, Clippy with -D warnings in the same three configurations, the aarch64-apple-darwin check, and the test adapter's Linux host and musl guest Clippy.

The gates, every step of check-nvx-rs on Windows and on Linux x86_64 with the host tests as root on Linux, and the VM suites on WHP and MSHV, run at the final heads of the stack once its reviews are clean.

Tests added, with a fake VMM in a process of its own, which listens beside the snapshot on Linux and holds the snapshot open sharing only reads on Windows, as OpenVMM does: the rendered arguments, the record, the claim, two clones of one generation launched at once, the socket's removal at teardown, at release, and after a crash, the refusals before any spawn, including a control socket path with , or = and a socket path that is not valid UTF-8, also once the payload's links are resolved, and crashes of the launch's caller before it begins, once it began, and once its VMM started. Also: the records of clones of every upper, network, and workload; the DNS server of a key's command line and its refusals; the end of the lock's reservation before a release that fails part of the way removes anything; the removal of the clone's files at release and at the sandbox's removal, durably and through the sandbox's directory, also when a link in its path names another sandbox meanwhile, or, for a release on Unix, once a directory in its path was renamed and a link to another sandbox put in its place, and, on Windows, while their deletion is pending, also by a retried release; a release's refusal of a path that names another directory than its lock's, removing nothing; a release that takes records and a binding whose deletion is pending, on Windows, for removed; the refusal of a plan of another guest address or prefix than the template's NIC; the refusal of a scratch image that is the memory of the snapshot of the leased or of another generation, through a hard link or by its own path, of one in the template store's directory, such as a file of a capture's snapshot, by its own path, through a link or a junction on the way to it, and on Linux through a held directory descriptor, and of one with another name; on Windows, the comparison with the store of a location that names its volume by its GUID, as for a volume with no drive letter or mount point; the refusal of the launch of a stale plan in RAM whose file a failed plan of the clone removed; the identities by which the stores tell files apart, through a path, an open file, and an entry of a directory, which are those of their seals; the opening of a generation's payload, which refuses a link, or a junction on Windows, in the place of the store's directory, of the directory of the generations, or of the payload, and a file in the place of the store's directory or of the payload, finds no payload in a store of another identity, or in another store or a directory of none in the store's place, and, on Unix, opens through a store's directory and a directory of the generations of mode 0300; on Unix, a release that a linked payload or store's directory refuses before it removes anything, and one that removes the socket from the payload that it opened once a link took the payload's place; on Unix, the clearing of a runtime record that refuses a linked payload or store's directory, keeping the record, and that leaves the files of another store in the store's place; and, on Unix, an acquisition's refusal of a payload of mode 0300 or 0500, leasing nothing, and a launch's refusal of a payload whose mode changed to 0500 once its lease was granted.

No VM: a real restore comes with PR 5's VM tests.

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.

🟡 Changes recommended

Cleanup can delete another running sandbox’s clone files, and clone requests can silently restore the wrong network address.

3 open issues
What changed in this PR

This PR extends NVX’s template-clone planning with activation records and supervised OpenVMM restore launches, implementing part 2 of #844. The Activate, Ready, and Start sequence remains for later PRs.

Changes:

  • Builds activation records from clone settings, binding metadata, and template resources.
  • Launches restores from leased snapshots with platform-specific readiness endpoints and scratch-claim inheritance.
  • Adds clone-file and socket cleanup, with tests for records, durability, and lifecycle behavior.
File Description
nvx-rs/​src/​host/​template/​store/​leases/​tests.rs Tests durable cleanup during lease release.
nvx-rs/​src/​host/​template/​store/​leases.rs Reserves clone sockets and removes launch artifacts on release.
nvx-rs/​src/​host/​template/​store/​capture.rs Shares snapshot and socket-limit constants.
nvx-rs/​src/​host/​template/​store.rs Documents clone sockets within generation payloads.
nvx-rs/​src/​host/​template/​mod.rs Exports and documents Restoring.
nvx-rs/​src/​host/​template/​key/​tests.rs Tests DNS extraction and malformed-token refusals.
nvx-rs/​src/​host/​template/​key.rs Exposes template DNS and network-policy metadata internally.
nvx-rs/​src/​host/​template/​clone/​tests.rs Tests activation records and unsupported settings.
nvx-rs/​src/​host/​template/​clone/​record.rs Builds activation records and binding identities.
nvx-rs/​src/​host/​template/​clone/​launch.rs Implements restore launches and the Restoring handle.
nvx-rs/​src/​host/​template/​clone.rs Adds activation settings and launch-device planning.
nvx-rs/​src/​host/​supervisor/​mod.rs Passes runtime records into clone-socket cleanup.
nvx-rs/​src/​host/​store/​tests.rs Tests selective cleanup of clone launch files.
nvx-rs/​src/​host/​store/​mod.rs Adds owned clone files, endpoint reservations, and cleanup.
nvx-rs/​src/​host/​store/​lifecycle/​tests.rs Extends removal tests for clone files and redirected paths.
nvx-rs/​src/​host/​store/​lifecycle.rs Removes clone files through the verified directory handle.
nvx-rs/​src/​host/​store/​lease.rs Centralizes generation-directory and clone-socket naming.
nvx-rs/​src/​host/​platform/​windows.rs Adds private named-pipe readiness listeners.
nvx-rs/​src/​host/​platform/​other.rs Adds unsupported-platform readiness stubs.
nvx-rs/​src/​host/​platform/​linux.rs Adds Unix-socket readiness listeners.
nvx-rs/​README.md Documents activation records, restore launches, and cleanup.

Review effort: Balanced

Comment thread nvx-rs/src/host/store/lifecycle.rs
Comment thread nvx-rs/src/host/template/clone/launch.rs
Comment thread nvx-rs/src/host/template/clone.rs Outdated

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.

🟡 Changes recommended

A failed lease release can leave a launch reservation active after clone files are deleted, allowing a VMM to start with a missing scratch disk.

3 open issues
3 resolved since last review

Review effort: Balanced

Comment thread nvx-rs/src/host/template/store/leases.rs Outdated
Comment thread nvx-rs/src/host/store/mod.rs Outdated
Comment thread nvx-rs/src/host/template/clone/launch.rs Outdated

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.

🟡 Changes recommended

Scratch validation can expose shared snapshot artifacts as writable devices, while failed replanning can leave older plans pointing to a missing scratch device.

2 open issues
3 resolved since last review

Review effort: Balanced

Comment thread nvx-rs/src/host/template/clone/launch.rs
Comment thread nvx-rs/src/host/template/clone/launch.rs

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.

🟡 Changes recommended

Scratch bindings can alias another generation’s snapshot, allowing clone writes to corrupt data used by other clones.

1 open issue
2 resolved since last review

Review effort: Balanced

Comment thread nvx-rs/src/host/template/clone.rs Outdated

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.

🟡 Changes recommended

Scratch claims can race with snapshot publication and corrupt a published snapshot, while non-UTF-8 socket paths are rejected only after launch side effects.

2 open issues
1 resolved since last review

Review effort: Balanced

Comment thread nvx-rs/src/host/template/clone.rs Outdated
Comment thread nvx-rs/src/host/template/clone/launch.rs

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.

🟡 Changes recommended

A symlink race during sandbox release can delete another sandbox’s clone files and binding despite the held lock.

4 open issues
2 resolved since last review

Review effort: Balanced

Comment thread nvx-rs/src/host/store/mod.rs Outdated
Comment thread nvx-rs/src/host/template/clone/launch.rs Outdated
Comment thread nvx-rs/src/host/template/clone/launch.rs
Comment thread nvx-rs/src/host/template/store.rs Outdated
Enrique Saurez and others added 7 commits October 28, 2026 16:46
A clone's plan builds the activation record that the clone's guest
applies in place of the configuration that its template captured, from
the request's workload view, overlay mode and journal policy, and from
the inputs that the plan checks: a restore section with a fresh
transaction ID of 16 random bytes and the lease's template reference;
the EROFS image with its binding's identity and the UUID that the
adapter's plan expects; a tmpfs, or the scratch image's binding identity
with the journal policy; no export; the network of the template's NIC,
with the policy identity that its key records, the DNS server that the
template's guest configured its resolver with, unpublished listeners
and the guest's proxy URL; and the template's processors and the memory
target. A binding's identity is the SHA-256 digest of a domain and of
the binding's serde form, which reads no file content.

TemplateKey::dns_server reads the DNS server from the virtnet_dns= token
of the key's effective command line, which OpenVMM adds when the NIC's
policy lets the guest reach the gateway's DNS server: once at most, for
a template with a NIC, naming one IP address in its canonical spelling.
Any other form is a malformed key, which planning a clone refuses as an
I/O error.

The plan refuses a GPT image, which a clone's guest does not assemble
yet, and settings that no record holds, before it opens or creates
anything.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
The sandbox store owns two more names for a clone's launch: its
activation record, clone-activation.bin, and the socket at which the
launch awaits the VMM's restore-ready event on Linux, restore.sock, as
long a name as control.sock.

A release removes them with the scratch device's file of a clone in
RAM, before the clone binding, whose durable removal makes theirs
durable too. A removal of the sandbox now does the same: it removes the
clone's files with the binding, before the sync that makes the
binding's absence durable, rather than with the store's other files
after it. Both take a clone's file whose deletion is pending, as a
Windows file system without POSIX deletion leaves one that a reader
holds, for removed, so a release that failed part of the way can be
retried.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
ClonePlan::launch runs the launch that a clone's lease reserves, under the
lock that acquired the lease, once the sandbox's clone binding records the
lease. The VMM restores the generation's snapshot in place, which nothing
copies, links, or changes, so clones of one generation restore it at once.
OpenVMM requires a restore's control socket in the snapshot's parent on
Linux, so the lease reserves the clone's control endpoint there:
<launch>.sock in the generation's payload, named after the launch, which
the journal and the runtime record name as any launch's endpoint. On
Windows the VMM listens at a fresh named pipe, as every launch does. The
launch refuses, before it writes anything, a socket that is not beside the
plan's snapshot, a payload whose mode is not exactly 0700, a socket path
that, with or without the payload's links resolved, would not fit a Unix
socket's address or is not valid UTF-8, as OpenVMM and the host take it,
and a control socket path that holds ',' or '=', which end the path in
OpenVMM's option listen=<path>.

The VMM restores a generation's snapshot in place while the clone's
guest writes its scratch image, so a plan refuses a scratch image that
could be a file of a snapshot. Once it claims the image, it refuses one
in the template store's directory, where the image is now, wherever the
links on its path lead: the store's captures write their snapshots
there, and their publications make them generations' by renaming
directories within it, while nothing that the host does moves a file
into the store. It refuses one with another name too, such as a hard
link to a file of a generation's snapshot, of a capture's, or of another
store's, which its location does not show. These checks open nothing in
the store, however many generations it holds. They resolve the file
system's own aliases, links, junctions, directories held open, and other
names, but not a mount or a network share that exposes the store's
directories elsewhere, so a store's directories must not be exposed that
way. On Windows, the location names a volume without a drive letter or
a mount point by its GUID. The launch also refuses, before it writes
anything, a plan in RAM whose scratch device's file is gone, as when a
later plan of the clone replaced it and failed.

It writes the clone's activation record to clone-activation.bin, listens
for the VMM's restore-ready event, and runs supervisor::launch with
OpenVMM's restore of the clone's devices: --restore-snapshot with the
generation's snapshot, --restore-memory with the memory target,
--restore-ready-path, the template's backend, CPU profile, and processors,
a boot console that discards its output, as cold boots have, and the
control console, without a kernel, command line, or guest address.

The VMM inherits a scratch image's claim: its descriptor on Linux, and on
Windows the image's guard, once the launch has closed the claim's handle,
which OpenVMM's open of the image, sharing no writing, would refuse.

The VMM removes its socket as it exits. The end of a launch and the
clearing of a runtime record remove one that an abrupt exit leaves, and a
release removes it and syncs the payload before it ends the lease, so a
payload holds clone sockets only while its generation has leases; a
retirement removes the payload with whatever a crash left in it. A
release ends the lock's reservation of the launch before it removes
anything, so one that fails part of the way leaves no launch of a plan
whose files it removed.

Tests run the launch with a fake VMM in a process of its own, which
listens beside the snapshot on Linux and holds the snapshot open sharing
only reads on Windows, as OpenVMM does: the rendered arguments, the
record, the claim, two clones of one generation launched at once, the
socket's removal at teardown, at release, and after a crash, the refusals
before any spawn, and crashes of the launch's caller before it begins,
once it began, and once its VMM started.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
…ate's

OpenVMM restores a clone's NIC, with the guest's address, from the
template's snapshot, and a clone's launch drops the --net of the
adapter's plan, so a plan that gave the guest another address or prefix
than the template's NIC planned a clone that silently ran on the
template's. ClonePlan::new now refuses such a plan with
ErrorKind::Unsupported: it compares the plan's network with the guest
network of the template key's NIC, TemplateKey::guest_network, in the
canonical form that both write.

The plan's policy must be the NIC's too, which OpenVMM checks as it
restores the NIC, refusing another one once the launch began, so a plan
of another policy fails closed. The key holds only OpenVMM's identity of
the policy, which nvx-rs does not compute, so the docs of
CloneRequest::adapter, of the restore's device arguments, and the README
say so. Which policy a template serves stays the network templates'
concern, one template per policy, whose key holds its identity.

Part of #844.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
…ectory

A release checked a sandbox, read its clone binding, and removed the
clone's files and the binding by the sandbox's path. A rename of one of
the path's directories, or a change of a link in it, as the release ran
could make it remove the files and the binding of another sandbox,
whose lock it does not hold, and end the lease that this one holds
still.

The release now opens one handle of the directory that the path names,
and checks through it that the directory holds the lock's file, as a
removal of the sandbox does. Through that handle, it checks that the
sandbox records no VM and no launch, reads the binding, removes the
clone's files and the binding, and makes their absence durable. A path
that names another directory than the lock's is refused, and nothing
is removed.

It reads the records and the binding through the handle as their
readers do at their paths: regular files that are not links, and on
Windows a file whose deletion is pending is removed already.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
The stores check that a path, or an entry of a directory that they
hold open, names the file whose lock they hold: as a lock is taken, as
a sandbox's store is removed, as a release opens the sandbox's
directory, and as a holder removes its lock file. A clone's plan finds
whether its scratch image is in the template store by comparing the
directories above the image with the store's. On Windows, all of these
compared the volume serial number and the 64-bit file index, which a
file system such as ReFS does not keep unique, so another file could
pass for the lock's, or a directory for the store's.

They now compare the identity that a file's seal holds, through
platform::file_id, opened_file_id and Directory::entry_id: the device
and inode on Linux, and on Windows the volume serial number and the
128-bit file ID of FILE_ID_INFO, which ReFS keeps unique too. The
64-bit identity stays where it is recorded: a launch's log claim, and
the identities of host paths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
…payload

On Linux, the control socket of a clone's VMM is in the payload of the
leased generation, generations/<generation> in the template store. A
release, a recovery of the clone's launch, and the clearing of its
runtime record removed the socket by its path, which follows a link
that took the place of the store's directory, of the directory of the
generations, or of the payload, so they could remove a file of another
directory, and the release then synced that directory.

They now open the payload through a handle of the store's directory,
following no link at the store's directory, at the directory of the
generations, or at the payload, and remove the socket through that
handle. A link, or a file, in the place of one of them is refused.
Through the handle of the store's directory, they first read the
store's identity record, and take a directory that holds another store,
or none, as one that holds no socket of the lease's: its files stay.
The identity record's reader moves to the module of the leases, beside
the directory of the generations, so that the sandbox's store reads it
without the template store's module.

The store's directory and the directory of the generations open with
the right to search them alone, which an owner-only directory may grant
without the right to read it, so that such a directory does not block
the removal; the payload opens for reading, so that its handle can make
the removal durable. A release opens the payload before it removes
anything, so that a refused link refuses it and nothing goes, and makes
the socket's absence durable through the payload's handle, whatever the
payload's path names meanwhile.

A release needs the payload to be a directory of the current user with
mode 0700, as a launch does: it opens the payload for reading, to make
the removal durable, and removes an entry from it. An acquisition now
refuses, on Linux, a payload that is not, so that it grants no lease
that its release could not end.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
@ppenna
Pedro Henrique Penna (ppenna) force-pushed the esaurez/844-pr2-activation-restore-launch-20261024 branch from 70f3b68 to df208b3 Compare October 28, 2026 23:54
Copilot AI balanced review requested due to automatic review settings October 28, 2026 23:54

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.

🟡 Changes recommended

Reserving existing caller-owned names can block sandbox access after upgrade, while incomplete symlink validation can leave leases unreleasable.

3 open issues

Review effort: Balanced

Comment on lines +342 to +343
CLONE_ACTIVATION_NAME,
RESTORE_READY_NAME,
@ppenna
Pedro Henrique Penna (ppenna) merged commit 667c28a into dev Oct 29, 2026
44 checks passed
@ppenna
Pedro Henrique Penna (ppenna) deleted the esaurez/844-pr2-activation-restore-launch-20261024 branch October 29, 2026 01:04
Pedro Henrique Penna (ppenna) pushed a commit that referenced this pull request Oct 29, 2026
On Linux, a clone's launch listens for the restore-ready event of its
VMM at a Unix socket in the sandbox's directory, which the bind creates
with the mode that the process's umask leaves. A restrictive umask,
such as 0200, could withhold even its owner's permission to write it,
and a VMM that is not root cannot connect to a socket without that
permission: the launch would then fail as if the VMM had sent nothing.

The launch now gives the socket mode 0600 right after the bind, before
it spawns the VMM. It opens the socket's path only to locate it,
following no link, refuses anything but a socket, and sets the mode
through the descriptor's link in /proc, as the store sets the modes of
the directories that it creates.

The review of #949, which added the restore-ready socket, found this.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
Pedro Henrique Penna (ppenna) pushed a commit that referenced this pull request Oct 29, 2026
On Linux, an acquisition refused a generation whose payload was not a
directory of the current user with mode 0700, but checked the payload
by its path, which follows links. The release of the lease opens the
payload through the template store's directory instead, following no
link at the store's directory, at the directory of the generations, or
at the payload. A link put in one of those places once the store was
opened thus let the acquisition grant a lease that its release could
not end until the link was gone.

The acquisition now opens the payload as the release does, through the
store's directory, whose identity it reads there, and checks the
payload's owner and mode through the payload's handle. Before it writes
anything, it refuses a payload that it cannot open that way, one that
is gone or whose store's directory holds another store, and one that is
not such a directory.

The review of #949, which made the release open the payload through a
handle, found this.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
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.

3 participants