Repository navigation
filesystem: share single host files without their directories - #475
Merged
Pedro Henrique Penna (ppenna) merged 6 commits intoOct 11, 2026
Merged
Pedro Henrique Penna (ppenna) merged 6 commits into
Pedro Henrique Penna (ppenna) merged 6 commits into
Conversation
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
File-kind enforcement and mount-target handling contain unresolved isolation and correctness issues.
2 open findings
What changed in this PR
Adds isolated single-file host sharing across NVX workloads using OpenVMM aggregate virtio-fs children.
Changes:
- Supports read-only/read-write file shares in
run, sandboxes, and ACI mappings. - Persists share kinds and updates guest mounting and snapshot behavior.
- Expands unit, hypervisor, smoke, CI, and documentation coverage.
| File | Description |
|---|---|
.github/workflows/run-nvx-microvm-tests.yml |
Tests file shares on Linux and Windows. |
aci_edge_sandboxes/README.md |
Documents ACI file mappings and limits. |
aci_edge_sandboxes/src/bin/aci-edge-sandboxes-fake-openvmm.rs |
Accepts file children in the fake VMM. |
aci_edge_sandboxes/src/openvmm/filesystem.rs |
Plans and records file-child exports. |
aci_edge_sandboxes/src/openvmm/launch.rs |
Updates child construction for recorded kinds. |
aci_edge_sandboxes/tests/openvmm_e2e.rs |
Adds real-hypervisor isolation coverage. |
aci_edge_sandboxes/tests/openvmm_fake.rs |
Tests generated file-child mappings. |
doc/ci.md |
Describes expanded CI coverage. |
doc/design/machine-and-device-abi.md |
Defines file-child and snapshot contracts. |
doc/design/sandbox-filesystem-and-agent-architecture.md |
Documents file-share architecture. |
doc/run.md |
Documents CLI behavior and constraints. |
doc/usage.md |
Updates mount option reference. |
guest/common/nvx-init-agent |
Creates and binds sandbox file targets. |
guest/common/nvx-hostmount |
Mounts and unmounts initramfs file shares. |
guest/common/nvx-sandbox-smoke |
Adds file-share smoke checks. |
openvmm |
Pins the file-child-capable OpenVMM revision. |
scripts/nvx.py |
Detects files and creates aggregate children. |
scripts/nvx_tools/microvm_tests.py |
Expands runtime and snapshot scenarios. |
scripts/nvx_tools/microvm_test_scripts/filesystem-shares-snapshot.sh |
Tests open file handles across snapshots. |
scripts/nvx_tools/microvm_test_scripts/filesystem-shares.sh |
Verifies isolation and access modes. |
scripts/nvx_tools/sandbox.py |
Models share kinds and aggregate tokens. |
scripts/nvx_tools/sandbox_lifecycle.py |
Adds managed configuration format 6. |
scripts/test_microvm_tests.py |
Updates simulated scenario tests. |
scripts/test_nvx_tools.py |
Adds CLI, agent, and persistence tests. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/fix-issue-282-single-file-mappings
branch
from
October 10, 2026 15:52
b8ad203 to
d45392c
Compare
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/fix-issue-282-single-file-mappings
branch
from
October 10, 2026 16:22
d45392c to
f32e3dd
Compare
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>
The upstream roadmap indexes the OpenVMM commits between its mapped tip and NVX's pin, which moves from 7bf0ee28b to 2130f24b5. Record the new pin, extend the branch DAG to position 308, and add positions 306 to 308, which export single host files as aggregate virtio-fs children, save their kinds, and request a file child with the `file` flag. Part of #282. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The OpenVMM backend exported the parent of every mapped file outside the mapped directories as a hidden aggregate child, which exposed only the mapped paths through --mount-deny and --mount-allow. Unmapped siblings stayed hidden, but the export still reached the parent directory, whose traverse-only root reported its host attributes, so a file grant became a narrowed directory grant. A file directly in a volume's root could not be mapped at all, because its parent would have been the whole volume. Plan each such file as a child of its own, which the pinned OpenVMM exports alone: Child records `file: true`, which serde omits for directories, so format-3 records without file children are unchanged, while earlier crates refuse a record with one. A file child needs no policy paths, so files in a volume's root may now be mapped, and every child's root is a mapping, so no planned child is hidden any more. Records of earlier crates may still hold hidden children, which start as before. A policy whose mapped directories and files would need more than the 256 children that OpenVMM offers is refused rather than served through the files' directories, which would grant more than the files. Two children that would export one object, such as two mapped hard links to one file, are refused, because OpenVMM refuses one object exported twice. A file grant must stay one. Each file child carries OpenVMM's `file` flag, so OpenVMM refuses a directory that replaced the file, and every start first checks that each child's root still has the kind that provision recorded, failing with backend_error otherwise. The fake OpenVMM accepts file children with the `file` flag, requires the flag to match the host object, and requires every policy path to lie inside a directory child. Unit tests cover files exported alone, hard links, a file inside a mapped directory, the serde round trip, the refusal beyond 256 children, OpenVMM's per-directory limits, and a start after a child changed its kind. A fake-backed test checks the exported children, the record, and the mapping table for the layout of #282, and that start refuses a granted file that a directory replaced. The new real-hypervisor test grants a read-only config/settings.json and a read-write results/output.txt, each beside a file that it does not grant: the workload reads the first, cannot write or remove it, rewrites the second, and finds only the granted file in each guest directory, while the host sees only the granted write. Document the export of single files, the kind checks, and the refusal in the README. Part of #282. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`--mount GUEST_TARGET,HOST_PATH[,ro|rw]` of run, sandbox run, and sandbox provision required a host directory, so a workload that needed one file, such as a read-only settings file or a read-write results file, got the file's whole directory. A --mount whose host path is a regular file now shares only that file. NVX attaches it as a child of the aggregate virtio-fs device, even when it is the only share, because the guest mounts the device's root, which is a directory. It passes OpenVMM's `file` flag for the child, so OpenVMM refuses a directory that replaced the file, and marks its token nvx_share=NAME,TARGET,MODE,file. The initramfs and the sandbox agent bind such a child at a regular file at its target, which they create, with its missing directories, when nothing is there. They refuse a link at the target before creating anything, and a target that holds anything else. Guests without this change fail closed on the token. The agent also reserves /etc/machine-id and everything below it, which the container runtime replaces with the workload's machine ID. A shared file takes no --mount-deny, --mount-allow, or --mount-write path, because its mode applies to the whole file, and the overlap checks compare host paths, so a shared file may lie beside a shared directory but not inside it, nor be a second hard link to another shared file. A managed sandbox with a shared file records each share's kind in configuration format 6, which earlier NVX releases refuse, and every start refuses a host path that no longer has the recorded kind. Unit tests cover the guest's file binds and link refusals, the launch contract, the run and sandbox commands, the managed configuration format, the reserved targets, and the kind checks at launch. Document file shares in run.md, usage.md, and the design documents, and rename "Live host-directory shares" to "Live host shares". Part of #282. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The filesystem-shares scenario shared three directories through the aggregate. It now also attaches a read-only config/settings.json and a read-write results/output.txt as file children with OpenVMM's `file` flag, each beside a file that no share exposes, and passes their nvx_share tokens with the file suffix. The guest requires the aggregate root to list the five children, with both file children as regular files, each file bind to be a virtiofs mount with its share's mode, and /config to hold only settings.json. A lookup below a file child fails with ENOTDIR. Appending to the settings fails with EROFS, both through the bind and, as guest root, through /run/nvx/shares. The workload rewrites the output file, which the aggregate then shows, but cannot remove it through /run/nvx/shares, which fails with EROFS, or rename it at its target. The host requires the guest's output and nothing else, including unchanged files beside the shared files. OpenVMM must reject, before boot, a file inside a directory child, a directory with the `file` flag, a file without it, a file as a plain --mount, and a policy path on a file child. The snapshot keeps the output file open across the capture and writes to it again after the restore, and a restore after the host replaced a shared file fails with a root identity mismatch. Part of #282. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The concurrent read-write and read-only shares step on Linux and Windows now also shares a read-only /config/settings.json and a read-write /results/output.txt, each beside a file that no share exposes, with the two directories, as four children of one aggregate. nvx-sandbox-smoke gains file-ro and file-rw modes, which require a virtiofs bind with the share's mode that is alone in its guest directory and holds the host's contents, refuse its removal and renaming, and either require a write to fail with EROFS or rewrite the file. The host lets every identity write both files, so only the read-only share can refuse a write, and requires the guest's rewrite of the read-write file and no other change, both for a one-shot run and for a managed sandbox, whose configuration must be format 6 and record each share's kind. Describe the coverage in ci.md and the smoke modes in run.md. Part of #282. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/fix-issue-282-single-file-mappings
branch
from
October 10, 2026 16:38
f32e3dd to
32d3cb3
Compare
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
The security-sensitive cross-platform behavior depends on still-open OpenVMM PR #130 and warrants final human review.
0 open findings
1 resolved since last review
🧠 Review effort: Balanced
This was referenced Oct 10, 2026
Pedro Henrique Penna (ppenna)
deleted the
agents/fix-issue-282-single-file-mappings
branch
October 11, 2026 17:02
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.



Fixes #282.
This PR lets an NVX workload be given a single host file, read-only or read-write, without the file's directory. The host enforces the access, and the file's siblings stay out of reach. It uses the file children of the aggregate virtio-fs device from nanvix/openvmm#130.
Why
A host directory was the smallest thing that NVX could share:
runandsandbox--mountrequired a directory, so a workload that neededconfig/settings.jsonreceived all ofconfig/.aci_edge_sandboxes, which MXC drives, mapped a file by exporting its parent directory with a hidden root (--mount-denyon the root,--mount-allowon the file). Unmapped siblings stayed hidden, but the guest's root still reached the parent itself: a traverse-only directory reports its host attributes, and the allowed name follows whatever the host puts there. A file grant thus became a narrowed directory grant, and a file directly in a volume's root could not be mapped, because its parent was the whole volume.Changes
4afaa6a4d2130f24b5, the head of nanvix/openvmm#130. Changes only the gitlink. `--mount-child NAME,HOST_PATH[,road2aa2567doc/openvmm-upstream-roadmap.mdrecords the new pin and indexes its three OpenVMM commits as positions 306-308.d8acaf02faci_edge_sandboxesexports each mapped file outside the mapped directories as a file child of its own (Child.file, which serde omits for directories, so records keepSTATE_FORMAT3), so files in a volume's root can now be mapped. A policy that would need more than 256 children is refused withpolicy_validationrather than served through the files' directories, so a broader grant is always the caller's explicit choice. Two children that would export one object, such as two hard links to one file, are refused. File children carry thefileflag, and everystartchecks that each child still has its recorded kind, so a directory that replaced a granted file is refused. The fake OpenVMM accepts file children, and new fake-backed and real-hypervisor tests cover #282's layout.a787963bfrunandsandbox--mountaccept a regular file. A shared file is always an aggregate child, even alone, attached with OpenVMM'sfileflag and the tokennvx_share=NAME,TARGET,MODE,file. The initramfs (nvx-hostmount) and the sandbox agent bind it at a regular file at the target, refuse a link there before creating anything, and refuse a target that is anything else. A shared file takes no--mount-deny,--mount-allow, or--mount-write, and may sit beside a shared directory but not inside one. Managed sandboxes record each share's kind in configuration format 6, andstartrefuses a host path whose kind changed./etc/machine-idand everything below it are now reserved targets.4db152f3ctest-microvm'sfilesystem-sharesscenario adds a read-onlyconfig/settings.jsonand a read-writeresults/output.txt, each beside a file that no share exposes. The workload checks that each file is all it can reach and that the host enforces each mode, including through/run/nvx/sharesas guest root. The scenario also covers snapshot capture with the output file open. Before the guest runs, OpenVMM must refuse a file inside a child, a directory with thefileflag, a file without it, a file--mount, a policy path on a file, and a restore after the host replaced a file.32d3cb3a6file-roandfile-rwmodes innvx-sandbox-smoke. It covers one-shot and managed (format 6) sandboxes.Updated docs:
aci_edge_sandboxes/README.md,doc/run.md,doc/usage.md,doc/ci.md,doc/design/machine-and-device-abi.md,doc/design/sandbox-filesystem-and-agent-architecture.md, anddoc/openvmm-upstream-roadmap.md. Indoc/run.md, "Live host-directory shares" is renamed "Live host shares".Behavior and compatibility
aci_edge_sandboxesrecords written by earlier crates, including hidden-parent children, still start as before.,filetoken, and older NVX releases reject configuration format 6.--mount(the guest mounts the device's root, a directory), can't be attached by a dormant-slot restore, and takes no policy paths. Anaci_edge_sandboxespolicy may need at most 256 children. Paths with commas remain unsupported (Support host filesystem paths containing commas #283).aci_edge_sandboxes, whose external library owns its mappings.OpenVMM promotion
mainat7bf0ee28b, whichdevalready pins. It has three commits:be5297817adds file children,87216077faddresses its review, and2130f24b5requires thefileflag for a file child. There are no other prerequisites.2130f24b5a51561cc6224fe5a0aff93cb121ac32, the same commit as that PR's head (treef8f510bf84126d72ca621496dadf2c60e7b40372), with a good SSH signature from ppenna@microsoft.com.4afaa6a4d, whose only parent isdevatcb0fba8ba.Validation
All results below are for
f32e3dd33, with OpenVMM2130f24b5. This head,32d3cb3a6, differs from it only in therunandsandboxhelp text and documentation, and in two laterdevcommits that change unrelated host-loopback tests; thevalidate-nvxunit tests pass at this head (901, 106 skipped).Run locally on Windows/WHP:
python scripts\nvx.py verify, and ruff, pyright (Linux and Windows), shellcheck, and shfmt.validate-nvxunit tests: 899 passed (106 skipped).cargo xtask fmt --fixmakes no changes.aci_edge_sandboxes: fmt, clippy (all features, and--lib --no-default-features), tests with both feature sets, doc, and the MSRV 1.89 check.test-microvmfilesystem-shares,filesystem-policy,denied-filesystem-paths, andfilesystem-snapshotpass.test-aci-edge-sandboxes: 15/15, including the newsingle_files_are_granted_without_their_directories.On bare-metal hosts, with OpenVMM built on the host (prometheus30 and prometheus28, with prometheus32 running prometheus30's Linux binary) and the guest artifacts above:
test-microvmfilesystem scenarios,test-aci-edge-sandboxes15/15, and the Linux CI step for concurrent sandbox shares (one-shot and managed) pass.validate-nvxunit tests 899 passed (13 skipped, so the POSIX shell tests of the agent,nvx-hostmount, and the smoke script ran).aci_edge_sandboxestests with all features pass. The fourtest-microvmfilesystem scenarios,test-aci-edge-sandboxes15/15, and the Linux CI step for concurrent sandbox shares pass.test-microvmfilesystem scenarios,test-aci-edge-sandboxes15/15, and the Windows CI step for concurrent sandbox shares pass.