Skip to content

microVM: export single host files as aggregate virtio-fs children - #130

Merged
ppenna merged 3 commits into
mainfrom
fix/aggregate-microvm-file-children-282
Oct 11, 2026
Merged

ppenna merged 3 commits into
mainfrom
fix/aggregate-microvm-file-children-282

Conversation

@ppenna

@ppenna ppenna commented Oct 10, 2026 •

Copy link
Copy Markdown

This PR lets a microVM aggregate export a single host file as a child, without the file's directory, for microsoft/nvx#282.

Why

An aggregate child could only be a host directory. To give a sandbox one file, such as a read-only config/settings.json or a read-write results/output.txt, NVX had to export the file's directory and hide everything else in it with --mount-deny and --mount-allow. Siblings stayed hidden, but the guest's root still reached the directory itself: a traverse-only directory reports its host attributes, and the allowed name follows whatever the host puts there. A file beside a directory child could not be exported without exporting its directory as a separate child.

Changes

Area Change
Command line --mount-child NAME,HOST_PATH[,ro|rw][,file] exposes a regular file with the trailing file flag, and a directory without it. The flag, not the host object, decides the kind: OpenVMM refuses a host path of the other kind, so a directory that replaced a shared file is never exported in its place. A file child takes no --mount-deny, --mount-allow, or --mount-write path, because its mode applies to the whole file. --mount and the ttrpc attachment stay directory-only, because the guest mounts the device's root, which must be a directory.
Resource MicrovmAggregateChild names its kind with the MicrovmAggregateChildKind enum (Directory or File), which openvmm_entry converts from the configuration's MicrovmFilesystemChildKind.
Device A file child's volume opens the file's parent directory, roots the child's inode at the file's name, and applies an internal policy that denies the directory and allows only that name. The volume names the file it exposes in an Option. The guest never receives a node of the directory: a lookup below the file fails with ENOTDIR, any other path with EACCES, and the synthetic root lists the child as a regular file (DT_REG). The child follows the file's name, so the guest sees a file that the host saves in its place, but anything other than a regular file at that name fails with EACCES, both at lookup and when the volume revalidates an operation's confined path.
Overlap Root identities are the file's. A file inside a directory child and two hard links to one file are refused, while a file beside a directory child, or beside another file, is accepted.
Snapshots The machine contract records each child's kind ("" for a directory, "file"), rejects unknown kinds, and requires a restore to keep every child's kind. The device saved state uses schema version 9 when a child is a file, so earlier releases refuse such a snapshot instead of restoring the file as a directory, and records each child's kind as a stable value (SAVED_DIRECTORY_CHILD or SAVED_FILE_CHILD), rejecting any other on restore. Aggregates of directories keep schema 8.
Commit Content
be5297817 File children, their tests, and the Guide's virtio-fs, CLI, and snapshot pages.
87216077f Review: the kind enum in the resource, the stable saved kind, the exposed file as an Option, and consistent refusals of policy paths on a file child.
2130f24b5 Review: the file flag, which makes a child's kind explicit and race-free, with command-line, restore, and Guide updates.

Compatibility

Additive: existing directory children, --mount, and their snapshots behave as before, and aggregates of directories still save schema 8. An older OpenVMM refuses a file in --mount-child and refuses schema 9, so it fails closed.

Validation

The branch sits on main at 7bf0ee28b, which includes #127. All results below are for this head, 2130f24b5.

  • On Windows, cargo clippy --all-targets, cargo doc --no-deps, and cargo nextest run --profile agent for virtiofs, virtio_resources, openvmm_defs, openvmm_helpers, and openvmm_entry: clean, with 435 tests passed. cargo xtask fmt --fix makes no changes. Device tests cover what a file child exposes, read-only refusals, a host that replaces the file, and saved-state restore, tampering, and unknown kinds, alongside resolver, contract, and command-line tests.
  • On Linux (prometheus30, bare metal, Azure Linux 3.0), the same nextest run: 481 passed, 1 skipped. Device tests and command-line tests also cover unknown saved kinds and a file flag that does not match the host object.
  • filesystem: share single host files without their directories microsoft/nvx#475, which pins this head, passed on local WHP and on bare-metal Linux/MSHV (prometheus30), Linux/KVM (prometheus32), and Windows/WHP (prometheus28):
    • the filesystem-shares, filesystem-policy, denied-filesystem-paths, and filesystem-snapshot scenarios of test-microvm. filesystem-shares adds a read-only and a read-write file child beside three directory children, checks from the guest what each file child exposes, captures a snapshot with the read-write file open and resumes it, and requires refused boots for a file inside a child, a file --mount, and a policy path on a file, and a refused restore after the host replaces a file;
    • the aci_edge_sandboxes lifecycle tests, 15 of 15;
    • the sandbox smoke test with two shared files beside two shared directories, both one-shot and managed.

The NVX CI covers KVM, MSHV, and WHP. Merge this PR before microsoft/nvx#475.

Part of microsoft/nvx#282.

An aggregate child exported only a host directory, so a sandbox that
needed one file, such as a read-only config/settings.json or a
read-write results/output.txt, had to export the file's directory and
hide everything else in it with --mount-deny and --mount-allow. The
guest's root could still reach that directory: a traverse-only
directory reports its host attributes, and the allowed name follows
whatever the host puts there. A file beside a directory child could not
be exported without exporting its directory as a second child.

Let --mount-child name a regular file. The child's volume opens the
file's parent directory, roots the child's inode at the file's name,
and applies an internal policy that denies the directory and allows
only that name, so the guest never receives a node of the directory: a
lookup below the file fails with ENOTDIR, any other path with EACCES,
and the synthetic root lists the child as a regular file. The child
follows the file's name, so the guest sees a file that the host saves in
its place, but anything other than a regular file at that name fails
with EACCES, both when a request resolves the name and when the volume
revalidates the confined path of an operation.

A file child takes no denied, allowed, or writable paths, because its
mode applies to the whole file. Its root identity is the file's, which
attach and restore validate as before, and the overlap rules compare
those identities, so a file inside a directory child and two hard links
to one file are refused, while a file beside a directory child is
accepted. --mount and the ttrpc attachment stay directory-only, because
the guest mounts the device's root, which must be a directory.

Record each child's kind in the machine contract, whose new field
rejects unknown kinds, and in the device's saved state, which uses
schema version 9 when a child is a file. Earlier releases refuse such a
snapshot instead of restoring the file as a directory, and a restore
requires every child to keep its kind.

Add device tests for file children, covering what the guest can reach,
read-only refusals, a host that replaces the file, and saved-state
restore and tampering, as well as resolver, contract, and command-line
tests. Update the Guide's virtio-fs, CLI, and snapshot pages.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 10, 2026 15:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

4 open findings
What changed in this PR

This PR extends microVM virtio-fs aggregate exports to support children that expose a single host regular file (without exposing the containing directory), including snapshot/restore compatibility safeguards.

Changes:

  • Add “file child” support to microVM aggregate profiles, resolver/device enforcement, and virtio-fs path confinement logic.
  • Introduce schema/versioning + snapshot/contract fields to ensure older OpenVMM builds fail closed and restores require matching child kinds.
  • Update CLI/docs/tests to cover file-child behaviors (policy-path restrictions, replacement on host, restore validation/tamper rejection).
File Description
vm/​devices/​virtio/​virtiofs/​src/​microvm/​state.rs Adds schema v9 selection/validation when any aggregate child is a file.
vm/​devices/​virtio/​virtiofs/​src/​microvm/​saved_state.rs Adds file flag to aggregate child saved state and introduces FILE_CHILD_SCHEMA_VERSION.
vm/​devices/​virtio/​virtiofs/​src/​microvm/​resolver.rs Resolves file children, rejects policy paths for them, and adds resolver tests.
vm/​devices/​virtio/​virtiofs/​src/​microvm/​profile.rs Adds “file child” concept (constructor + identity helpers) and validates opened roots accordingly.
vm/​devices/​virtio/​virtiofs/​src/​microvm/​inode.rs Enforces “file-only” visibility and revalidation rules within a volume.
vm/​devices/​virtio/​virtiofs/​src/​microvm/​fs.rs Opens file-child volumes by opening parent dir + rooting at filename, with internal allow-only policy.
vm/​devices/​virtio/​virtiofs/​src/​microvm/​aggregate_fs_tests.rs Adds device-level tests for file-child exposure, refusals, replacement, and restore behavior.
vm/​devices/​virtio/​virtiofs/​src/​inode.rs Adds root_entry to VirtioFsVolume to model “root is file entry” vs “root is directory”.
vm/​devices/​virtio/​virtiofs/​src/​aggregate.rs Makes aggregate root listing correctly report DT_REG for file children and uses root_entry.
vm/​devices/​virtio/​virtio_resources/​src/​fs/​microvm.rs Extends resource profile child structure with file: bool.
openvmm/​openvmm_helpers/​src/​snapshot/​microvm.rs Adds snapshot contract kind field for children and parsing/validation for restore.
openvmm/​openvmm_entry/​src/​microvm/​filesystem.rs Allows --mount-child to accept files, enforces “no policy paths for file child”, and root overlap rules.
openvmm/​openvmm_entry/​src/​microvm/​config.rs Plumbs child file flag into virtio resource config generation.
openvmm/​openvmm_entry/​src/​cli_args/​microvm.rs Updates CLI docs/help text for file-child capability and constraints.
openvmm/​openvmm_defs/​src/​microvm.rs Introduces MicrovmFilesystemChildKind enum + config APIs (new_file, is_file) and validation.
Guide/​src/​user_guide/​openvmm/​snapshots.md Documents snapshot contents/restore requirements for child kinds (dir vs file).
Guide/​src/​reference/​openvmm/​management/​cli.md Documents --mount-child file semantics and constraints.
Guide/​src/​reference/​devices/​virtio/​virtio-fs.md Documents single-file children behavior, constraints, and error semantics.

🧠 Review effort: Lite


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

Comment thread vm/devices/virtio/virtio_resources/src/fs/microvm.rs
Comment thread vm/devices/virtio/virtiofs/src/microvm/saved_state.rs Outdated
Comment thread vm/devices/virtio/virtiofs/src/inode.rs Outdated
Comment thread vm/devices/virtio/virtiofs/src/microvm/resolver.rs Outdated
Part of microsoft/nvx#282.

Address review feedback on the file children of a microVM aggregate:

- The virtio-fs resource names a child's kind with the
  MicrovmAggregateChildKind enum instead of a `file` flag, which
  openvmm_entry converts from the configuration's child kind.
- The device saved state records a child's kind as a stable value,
  SAVED_DIRECTORY_CHILD or SAVED_FILE_CHILD, in the field that held the
  flag, which it encodes the same way, and a restore rejects any other
  value instead of taking it for a known kind. Schema 8 states, which
  predate the field, still mean directories.
- A volume names the regular file that it exposes in an Option instead
  of treating an empty root path as its directory, and its callers ask
  for the exposed file instead of testing for an empty path.
- Every refusal of policy paths on a file child says that a file takes
  no denied, allowed, or writable paths, because its access mode
  applies to the whole file.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ppenna added a commit to microsoft/nvx that referenced this pull request Oct 10, 2026
87216077f (fix/aggregate-microvm-file-children-282 in nanvix/openvmm,
nanvix/openvmm#130) stacks on 7bf0ee28b, which dev already pins. It lets
--mount-child NAME,HOST_PATH[,ro|rw] export a regular host file, without
its directory, as a child of the aggregate virtio-fs root.

A file child's volume opens the file's parent directory but roots the
child at the file's name and hides everything else, so the guest never
receives a node of the directory: a lookup below the file fails with
ENOTDIR, any other path with EACCES, and the aggregate root lists the
child as a regular file. The child follows the file's name, so the guest
sees a file that the host saves in its place, but anything other than a
regular file at that name fails with EACCES. A file child takes no
--mount-deny, --mount-allow, or --mount-write path, a file inside a
directory child or a second hard link to it is refused, and --mount
stays directory-only, because the guest mounts the device's root.

The snapshot contract records each child's kind, a restore requires
every child to keep it, and the device saves schema version 9 when a
child is a file, which earlier releases refuse. The saved state records
each child's kind as a stable value and rejects one that it does not
know.

run, sandbox, and aci_edge_sandboxes still export only directories; the
following commits let them share single files.

Part of #282.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Part of microsoft/nvx#282.

--mount-child decided a child's kind from its host path, so a regular
file that a directory replaced after its caller had checked it was
exported as the whole directory, which broadens a file grant into a
directory grant.

Make the kind explicit: --mount-child NAME,HOST_PATH[,ro|rw][,file]
exposes a regular file only with the trailing `file` flag, and a
directory otherwise. OpenVMM refuses a host path of the other kind, and
the device then validates the opened root's kind and identity against
it, so the guest gets the kind that the caller requested or nothing. A
restore must request the kinds that the snapshot recorded.

Update the command-line help and the Guide's virtio-fs and CLI pages.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ppenna added a commit to microsoft/nvx that referenced this pull request Oct 10, 2026
2130f24b5 (fix/aggregate-microvm-file-children-282 in nanvix/openvmm,
nanvix/openvmm#130) stacks on 7bf0ee28b, which dev already pins. It lets
--mount-child NAME,HOST_PATH[,ro|rw][,file] export a regular host file,
without its directory, as a child of the aggregate virtio-fs root.

The `file` flag, not the host object, sets a child's kind: OpenVMM
refuses a host path of the other kind, so a directory that replaced a
shared file is never exported in its place. A file child's volume opens
the file's parent directory but roots the child at the file's name and
hides everything else, so the guest never receives a node of the
directory: a lookup below the file fails with ENOTDIR, any other path
with EACCES, and the aggregate root lists the child as a regular file.
The child follows the file's name, so the guest sees a file that the
host saves in its place, but anything other than a regular file at that
name fails with EACCES. A file child takes no --mount-deny,
--mount-allow, or --mount-write path, a file inside a directory child
or a second hard link to it is refused, and --mount stays
directory-only, because the guest mounts the device's root.

The snapshot contract records each child's kind, a restore requires
every child to keep it, and the device saves schema version 9 when a
child is a file, which earlier releases refuse. The saved state records
each child's kind as a stable value and rejects one that it does not
know.

run, sandbox, and aci_edge_sandboxes still export only directories; the
following commits let them share single files.

Part of #282.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ppenna added a commit to microsoft/nvx that referenced this pull request Oct 10, 2026
2130f24b5 (fix/aggregate-microvm-file-children-282 in nanvix/openvmm,
nanvix/openvmm#130) stacks on 7bf0ee28b, which dev already pins. It lets
--mount-child NAME,HOST_PATH[,ro|rw][,file] export a regular host file,
without its directory, as a child of the aggregate virtio-fs root.

The `file` flag, not the host object, sets a child's kind: OpenVMM
refuses a host path of the other kind, so a directory that replaced a
shared file is never exported in its place. A file child's volume opens
the file's parent directory but roots the child at the file's name and
hides everything else, so the guest never receives a node of the
directory: a lookup below the file fails with ENOTDIR, any other path
with EACCES, and the aggregate root lists the child as a regular file.
The child follows the file's name, so the guest sees a file that the
host saves in its place, but anything other than a regular file at that
name fails with EACCES. A file child takes no --mount-deny,
--mount-allow, or --mount-write path, a file inside a directory child
or a second hard link to it is refused, and --mount stays
directory-only, because the guest mounts the device's root.

The snapshot contract records each child's kind, a restore requires
every child to keep it, and the device saves schema version 9 when a
child is a file, which earlier releases refuse. The saved state records
each child's kind as a stable value and rejects one that it does not
know.

run, sandbox, and aci_edge_sandboxes still export only directories; the
following commits let them share single files.

Part of #282.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ppenna
ppenna merged commit 2130f24 into main Oct 11, 2026
@ppenna
ppenna deleted the fix/aggregate-microvm-file-children-282 branch October 11, 2026 17:02
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