Repository navigation
microVM: export single host files as aggregate virtio-fs children - #130
Merged
Merged
Conversation
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>
There was a problem hiding this comment.
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
The newfile: boolflag is a “boolean trap” and duplicates the newly introduced… · New Saved state also encodes child kind asbool. Even with schema gating, using a boolean makes… · Newroot_entry: PathBufuses “empty path means directory-root” as a sentinel, and multiple call sites… · New The new error message is a bit underspecified for users because it doesn’t say which… · New
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.
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>
This was referenced Oct 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


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.jsonor a read-writeresults/output.txt, NVX had to export the file's directory and hide everything else in it with--mount-denyand--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
--mount-child NAME,HOST_PATH[,ro|rw][,file]exposes a regular file with the trailingfileflag, 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-writepath, because its mode applies to the whole file.--mountand the ttrpc attachment stay directory-only, because the guest mounts the device's root, which must be a directory.MicrovmAggregateChildnames its kind with theMicrovmAggregateChildKindenum (DirectoryorFile), whichopenvmm_entryconverts from the configuration'sMicrovmFilesystemChildKind.Option. The guest never receives a node of the directory: a lookup below the file fails withENOTDIR, any other path withEACCES, 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 withEACCES, both at lookup and when the volume revalidates an operation's confined path.""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_CHILDorSAVED_FILE_CHILD), rejecting any other on restore. Aggregates of directories keep schema 8.be529781787216077fOption, and consistent refusals of policy paths on a file child.2130f24b5fileflag, 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-childand refuses schema 9, so it fails closed.Validation
The branch sits on
mainat7bf0ee28b, which includes #127. All results below are for this head,2130f24b5.cargo clippy --all-targets,cargo doc --no-deps, andcargo nextest run --profile agentforvirtiofs,virtio_resources,openvmm_defs,openvmm_helpers, andopenvmm_entry: clean, with 435 tests passed.cargo xtask fmt --fixmakes 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.fileflag that does not match the host object.filesystem-shares,filesystem-policy,denied-filesystem-paths, andfilesystem-snapshotscenarios oftest-microvm.filesystem-sharesadds 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;aci_edge_sandboxeslifecycle tests, 15 of 15;The NVX CI covers KVM, MSHV, and WHP. Merge this PR before microsoft/nvx#475.