Skip to content

filesystem: share single host files without their directories - #475

Merged
Pedro Henrique Penna (ppenna) merged 6 commits into
devfrom
agents/fix-issue-282-single-file-mappings
Oct 11, 2026
Merged

Pedro Henrique Penna (ppenna) merged 6 commits into
devfrom
agents/fix-issue-282-single-file-mappings

Conversation

@ppenna

@ppenna Pedro Henrique Penna (ppenna) commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

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:

  • run and sandbox --mount required a directory, so a workload that needed config/settings.json received all of config/.
  • aci_edge_sandboxes, which MXC drives, mapped a file by exporting its parent directory with a hidden root (--mount-deny on the root, --mount-allow on 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

Commit Change
4afaa6a4d Pins OpenVMM 2130f24b5, the head of nanvix/openvmm#130. Changes only the gitlink. `--mount-child NAME,HOST_PATH[,ro
ad2aa2567 doc/openvmm-upstream-roadmap.md records the new pin and indexes its three OpenVMM commits as positions 306-308.
d8acaf02f aci_edge_sandboxes exports each mapped file outside the mapped directories as a file child of its own (Child.file, which serde omits for directories, so records keep STATE_FORMAT 3), so files in a volume's root can now be mapped. A policy that would need more than 256 children is refused with policy_validation rather 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 the file flag, and every start checks 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.
a787963bf run and sandbox --mount accept a regular file. A shared file is always an aggregate child, even alone, attached with OpenVMM's file flag and the token nvx_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, and start refuses a host path whose kind changed. /etc/machine-id and everything below it are now reserved targets.
4db152f3c test-microvm's filesystem-shares scenario adds a read-only config/settings.json and a read-write results/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/shares as 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 the file flag, a file without it, a file --mount, a policy path on a file, and a restore after the host replaced a file.
32d3cb3a6 The CI step for concurrent sandbox shares (Linux and Windows) adds both files beside the two directories, with new file-ro and file-rw modes in nvx-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, and doc/openvmm-upstream-roadmap.md. In doc/run.md, "Live host-directory shares" is renamed "Live host shares".

Behavior and compatibility

  • The guest reaches a shared file only at its target. It can't list, inspect, or change the file's directory or siblings. A read-write file can be rewritten in place but not replaced, renamed, or removed. The child follows the file's name, so the guest sees a file that host software saves over it.
  • Shares with directories only keep their current behavior, and so do their snapshots and configuration formats. aci_edge_sandboxes records written by earlier crates, including hidden-parent children, still start as before.
  • Older components fail closed:
    • Older guests reject the ,file token, and older NVX releases reject configuration format 6.
    • Older crates reject sandbox records that hold a file child.
    • Older OpenVMM releases refuse a file child and schema 9 device state.
  • Limits: a shared file can't be a plain --mount (the guest mounts the device's root, a directory), can't be attached by a dormant-slot restore, and takes no policy paths. An aci_edge_sandboxes policy may need at most 256 children. Paths with commas remain unsupported (Support host filesystem paths containing commas #283).
  • Out of scope: the opt-in agent backend of aci_edge_sandboxes, whose external library owns its mappings.

OpenVMM promotion

Validation

All results below are for f32e3dd33, with OpenVMM 2130f24b5. This head, 32d3cb3a6, differs from it only in the run and sandbox help text and documentation, and in two later dev commits that change unrelated host-loopback tests; the validate-nvx unit 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-nvx unit tests: 899 passed (106 skipped).
  • OpenVMM: clippy, doc, and nextest for the five changed crates, 435 passed; cargo xtask fmt --fix makes 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.
  • With OpenVMM and the Alpine initramfs rebuilt from this head, and the other guest artifacts from the same sources:
    • test-microvm filesystem-shares, filesystem-policy, denied-filesystem-paths, and filesystem-snapshot pass.
    • test-aci-edge-sandboxes: 15/15, including the new single_files_are_granted_without_their_directories.
    • The updated CI step for concurrent sandbox shares on Windows passes, one-shot and managed.

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:

Host Platform Results
prometheus30 Linux/MSHV (Azure Linux 3.0) OpenVMM nextest for the five changed crates: 481 passed, 1 skipped. The four test-microvm filesystem scenarios, test-aci-edge-sandboxes 15/15, and the Linux CI step for concurrent sandbox shares (one-shot and managed) pass.
prometheus32 Linux/KVM (Ubuntu, Linux 7.0) validate-nvx unit tests 899 passed (13 skipped, so the POSIX shell tests of the agent, nvx-hostmount, and the smoke script ran). aci_edge_sandboxes tests with all features pass. The four test-microvm filesystem scenarios, test-aci-edge-sandboxes 15/15, and the Linux CI step for concurrent sandbox shares pass.
prometheus28 Windows/WHP (HPE ProLiant DL380 Gen10) The four test-microvm filesystem scenarios, test-aci-edge-sandboxes 15/15, and the Windows CI step for concurrent sandbox shares pass.

Copilot AI balanced review requested due to automatic review settings October 10, 2026 15:10
@ppenna Pedro Henrique Penna (ppenna) changed the title Share single host files without their directories filesystem: share single host files without their directories Oct 10, 2026

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

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.

Comment thread aci_edge_sandboxes/src/openvmm/filesystem.rs Outdated
Comment thread guest/common/nvx-hostmount Outdated
Copilot AI balanced review requested due to automatic review settings October 10, 2026 15:52
@ppenna
Pedro Henrique Penna (ppenna) force-pushed the agents/fix-issue-282-single-file-mappings branch from b8ad203 to d45392c Compare October 10, 2026 15:52

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

File-kind enforcement and machine-ID descendant validation currently permit unsafe or invalid mappings.

5 open findings
2 resolved since last review

🧠 Review effort: Balanced

Comment thread aci_edge_sandboxes/src/openvmm/filesystem.rs
Comment thread guest/common/nvx-init-agent Outdated
Comment thread scripts/nvx_tools/sandbox.py Outdated
Comment thread aci_edge_sandboxes/src/openvmm/filesystem.rs
Comment thread doc/openvmm-upstream-roadmap.md 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.

🔵 Needs a closer look

It spans an upstream dependency, persistent formats, guest mount logic, snapshots, and multiple hypervisors while exact-head validation remains pending.

1 open finding
5 resolved since last review

🧠 Review effort: Balanced

Comment thread scripts/nvx.py
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>
@ppenna
Pedro Henrique Penna (ppenna) force-pushed the agents/fix-issue-282-single-file-mappings branch from f32e3dd to 32d3cb3 Compare October 10, 2026 16:38
Copilot AI balanced review requested due to automatic review settings October 10, 2026 16:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The 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

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.

Support individual-file filesystem mappings

2 participants