Repository navigation
Sandbox mode never mounts the live virtio-fs share (--mount) inside the workload container #216
Description
Summary
In sandbox mode (nvx_sandbox=1, --microvm-sandbox-block), the guest never mounts the live virtio-fs share, even though OpenVMM attaches it and passes its parameters on the kernel command line. A workload running in the sandbox container can't see a host directory exported with --mount.
This blocks github/gh-aw-firewall (AWF) from giving NVX the same workspace-sharing model as its Cloud Hypervisor backend. Cloud Hypervisor live-shares the job workspace read-write, and optionally the tool cache read-only, over virtio-fs. With NVX, AWF currently has to stage the workspace into an EROFS layer and copy results back after the VM exits.
Current behavior
References are to dev @ 20ce852. The same behavior is in the currently pinned v0.1.0-dev.d561c4300ebe, under alpine/.
Host side (works). OpenVMM accepts --mount GUEST_TARGET,HOST_PATH,ro|rw and --mount-deny HOST_PATH together with --microvm-sandbox-block, and validation doesn't reject the combination. openvmm_defs/src/config.rs appends virtfs_dir=<target> virtfs_tag=microvm virtfs_mode=<ro|rw> to the kernel command line, and the guest logs virtiofs virtio1: discovered new tag: microvm.
Guest side (missing).
guest/common/init(~L111-116) callsmount_virtfs(/sbin/nvx-hostmount) only whensandbox_mode=false. In sandbox mode it goes straight toexec /sbin/nvx-init-agent(~L183-187).guest/common/nvx-init-agentassembles the container overlay at$rootfs(~L208-210) and starts the workload withnvx-container-launch→nvx-container-enter(~L277). It never readsvirtfs_*or mounts themicrovmtag.- The workload can't mount the share itself.
nvx-container-enterruns it throughsetprivwith--bounding-set=-all --inh-caps=-all --ambient-caps=-allinside a private mount namespace, which is correct and should stay that way.
As a result, the share is attached but unreachable from the sandboxed workload.
Requested change
When virtfs_tag is present in sandbox mode, nvx-init-agent (or nvx-container-enter) should mount the share inside the container rootfs at virtfs_dir before dropping privileges. For example:
- Parse
virtfs_tag,virtfs_dirandvirtfs_modethe same waynvx-hostmountdoes. - Validate
virtfs_dir: it must be absolute, must not contain.., and must not be/,/proc,/sys,/dev, or/.nvx-agent. - After the overlay is built, run
mkdir -p "$rootfs$virtfs_dir"andmount -t virtiofs -o "$virtfs_mode,nosuid,nodev" microvm "$rootfs$virtfs_dir", so the mount survives the--make-rprivate/unshare --mount/chroot. - Unmount it during teardown, before the overlay unmount (~L295). If the mount fails, report it as a sandbox error (fail closed) rather than silently running without the share.
- Keep the existing sandbox behavior unchanged when no
--mountis given.
Guest-side requirements:
- UID mapping: the workload identity (
--microvm-workload-identity, for example uid/gid 1001runner) must be able to read and write files in anrwshare, and files it creates should be owned by the host user that owns the export. The host-side uid/gid behavior of the built-in virtio-fs server should be documented. --mount-denypaths must remain hidden inside the container.- An
roshare must reject writes from the workload (EROFS/EACCES). - A second read-only share (for example a tool cache at
/opt/hostedtoolcache) would be ideal for full parity with AWF'sworkspace-and-tool-cachepolicy. The microVM currently has a single fixedmicrovmslot, so please say whether multiple shares are feasible or whether a single share is the intended contract.
Acceptance criteria
- A microVM test such as
scripts/nvx_tools/microvm_test_scripts/filesystem-live.sh, run in sandbox mode with--mount /workspace,<dir>,rw, shows that:- a host-created file is visible in the workload;
- a file written by the workload appears on the host immediately, with no copy-back;
- a host edit made while the workload runs is visible to the workload;
--mount-denypaths are absent;- a
romount rejects writes.
- A new
v0.1.0-dev.*release (package plusSOURCE-MANIFEST.json) that includes the change, so AWF can bump its pinnednvxCommit/openvmmCommit. doc/run.md(virtio-fs host mapping section) anddoc/design/current-limits.mddocument sandbox-mode support.
AWF follow-up (for context)
Once a release is available, AWF will:
- pass
--mount /workspace,$GITHUB_WORKSPACE,rw, plus--mount-denyfor credential paths, insrc/nvx/runtime-lifecycle.ts; - drop the workspace-staging and copy-back path;
- add a
smoke-nvx-virtiofshardware smoke test covering the criteria above.
Activity
lpcox commentedon Sep 25, 2026
Prototype: a guest-only fix works (tested on KVM)
I patched the pinned
v0.1.0-dev.d561c4300ebeinitramfs and booted it on a GitHub-hostedubuntu-latestKVM runner. OpenVMM ran with AWF's sandbox arguments (--microvm-sandbox-block,--microvm-workload-identity 1001:1001,--microvm-lifecycle one-shot,--single-process) plus--mount /workspace,<dir>,rw|ro --mount-deny <dir>/secrets.The patch doesn't unpack or modify the base image: it appends a second root-owned cpio member that overrides
/sbin/nvx-init-agent. No OpenVMM change was needed.Run: https://github.com/github/gh-aw-firewall/actions/runs/36199851844
rw/workspace, the host file isn't visible, and guest writes fail. The guest still logsvirtiofs virtio0: discovered new tag: microvm.rw--mount-denyhidessecrets/. The host sawguest.txtwhile the VM was running, then wrotehost-edit.txt, and the guest saw that edit live.roro,nosuid,nodev, the host file is visible, the denied path is hidden, and writes are rejected.Every patched run reported
outcome.category: successandteardown.virtiofs_released: true.Patch (
nvx-init-agent, right after the container overlay is assembled)This needs no changes to
nvx-container-enter. The mount lives under$rootfs, so it survives--make-rprivate,unshare --mountandchroot. The workload still runs with every capability dropped.Open issue found: host-side ownership of guest-created files
A file created by the uid 1001 workload appeared on the host as
0:0(OpenVMM runs as root). For AWF's use case, the workspace belongs to the runner user, so files created in the guest should be owned by the export owner, or by a configurable host uid/gid, rather than root. Could the built-in virtio-fs server map creates to the export root's owner (or accept an option for this), like virtiofsd's--translate-uid/--translate-gid?lpcox commentedon Sep 25, 2026
Follow-up: guest-created files are owned by the VMM identity on the host — prototype fix (OpenVMM)
With the sandbox mount from the previous comment in place, files the workload creates on the share appear on the host as the OpenVMM process identity (
0:0when run as root, or the dedicated VMM account in AWF), not the guest caller. Cause: OpenVMM's lxutil Unix backend ignoresLxCreateOptionsuid/gid ("uid/gid are never used"), and every other operation also runs as the VMM identity. Side effects beyond ownership: a nested write into a guest-created directory fails (dir isroot 0755and the guest kernel enforces it), andchmodon own files fails.Proposed design (prototyped against nanvix/openvmm
0bc357bb)--mount-owner process|caller(defaultprocess= current behavior; requires--mount).calleris carried as HostFsmount_options = "owner=caller"(the microVM resolver previously rejected any mount options).callermode,process_virtiofs_requestwraps the entire synchronousSession::dispatchin a per-threadsetfsuid/setfsgidswitch to the FUSE request's uid/gid — the same model as virtiofsd. So create, append, truncate, rename, unlink, chmod, … are all permission-checked and owned as the guest caller.FORGET/BATCH_FORGET/DESTROYskip the switch.-1; if it didn't take effect (noCAP_SETUID/CAP_SETGID) the request getsEPERMand nothing runs as the VMM identity.lxutil::FsCredentials(Unix-only scoped guard, restores on drop). Unit tests: owner option parsing, root-squash mapping, switch/restore, fail-closed without capability.Results on a KVM runner (patched initramfs + patched OpenVMM, workload uid 1001)
--mount-denycargo test -p lxutil -p virtiofs: all pass. The third column is the configuration AWF would ship: VMM as a dedicated non-root account with onlyCAP_SETUID+CAP_SETGID— everything except symlinks works, and ownership matches the guest user.Asks / notes for the NVX release
--mount-owner callerto the OpenVMM NVX builds, and have NVX pass it for sandbox--mount.CAP_SETUID/CAP_SETGIDwould also permitsetuid(0)from a compromised VMM. Recommend OpenVMM's seccomp filter (or the embedder's) allowsetfsuid/setfsgidbut denysetuid/setgid/setreuid/setregid/setresuid/setresgid/setgroupsin caller mode. Onlysetfsuid/setfsgidare used by the patch.chmod u+son the caller's own file succeeds (host sees-rwSowned by 1001, never root thanks to squash). virtiofsd clears suid/sgid on write withkillpriv_v2; consider similar, or document.ENOTSUPby design on the microVM profile (lib.rssymlink()whenis_microvm()). This isn't an ownership issue, but it is a parity gap vs. Cloud Hypervisor/virtiofsd:npm ci(node_modules/.bin), many build tools, and git checkouts with symlinks will fail on a live-shared workspace. Could symlinks be allowed with targets stored verbatim (resolution already happens guest-side, and the host side uses strict/O_NOFOLLOWpath walking)?openvmm-mount-owner.patch (against nanvix/openvmm 0bc357bb)
lpcox commentedon Sep 26, 2026
Baseline from the AWF NVX build smoke (release
v0.1.0-dev.d561c4300ebe, no virtio-fs)github/gh-aw-firewall#9027, run 36203009609: all checks pass (
npm ci863 packages +npm run build+ Jest subset, Go build/test, network isolation). This is today's staging + copy-back path. Details that affect the sandbox--mountimplementation:nvx_workload_uid=1001 nvx_workload_gid=1001, which is the runner uid that owns the workspace on the host. With--mount-owner caller, FUSE requests arrive as 1001, and root-squash maps to the export owner, which is also 1001. So AWF needs no uid translation. Please keepnvx_workload_uid/gidas the identity that FUSE requests carry, i.e. no user namespace remap inside the guest.tar /workspace -> /tmp/nvx-build/repo, excludingnode_modules/.git/dist). This was done to keepnode_modulesout of the layer/copy-back. With a live share, the natural thing is to build in place, andnpm cithen createsnode_modules/.bin/*symlinks. The microVM profile'ssymlink()→ENOTSUPwould make that fail. This is the most important parity item after ownership; Cloud Hypervisor + virtiofsd builds in place fine..npmrc(and similar) from every layer, including the workspace, so the smoke has to restatelegacy-peer-depsvia env. For--mount, please state the policy explicitly. Options: (a) a live share shows host files as-is and the embedder uses--mount-denyfor secrets (virtiofsd parity; AWF would prefer this), or (b) a built-in deny list. If (b), make it overridable, and make it exact paths rather than a name pattern.--runInBand. Metadata-heavy workloads (npm ci= tens of thousands of files) will be sensitive to the virtio-fs cache mode. For reference, AWF runs virtiofsd with--cache=auto --inode-file-handles=never. Please document which cache/attr timeouts sandbox--mountuses.virtio_mmiodevices at0xd0000000..0xd0006000for console, net, the distro layer (vda), the custom layer (vdb) and scratch ext4 (vdc). The patched init from the earlier comment readsvirtfs_tag/virtfs_dir/virtfs_modefrom the cmdline and mounts after the overlay is built. It must also be unmounted before the scratchEXT4-fs (vdc): unmountingstep at teardown, as the prototype does./runtmpfs sizing for it (the smoke has to grow/run), and copy-back (applied 8 path(s)here). A copy-back step that silently dropped files was a debugging pain point; a live share removes that class of bug.registry.npmjs.org,github.comallowed;example.comdenied). A virtio-fs share adds no network path. Please confirm the--mountdevice has no effect on the netns / egress policy.virtiofs: perform microVM HostFs requests as the guest caller
huzaifa-d commentedon Sep 30, 2026
Rechecked this on Windows/WHP on 30 September with NVX
e0a3ddaf0b3a83f43aed765147bb329b57217862and OpenVMM885fde30f334dc18c1042a0acfe6a1b43c9a7f2b, using the stockv0.1.0-dev.84b8d9d3e3faruntime. The release and selected source have matching runtime/guest inputs; their differences are documentation and benchmark data.The mount-integration gap is still reproducible:
nvx.py sandboxCLI--mountis rejected as an unrecognized argument.65534:65534could not see/workspaceor its known host marker. This occurred with RW, RO and denied-subpath configurations.The sandbox workload reported:
No runtime or guest-initialization code was patched. The test entrypoint was supplied in an owned scratch image. Host share contents remained unchanged and the VMs were cleaned up.
This confirms missing workload mount visibility; it does not count failed sandbox reads/writes as successful RO or denied-path enforcement. These checks were Windows-only and do not assess the Linux ownership or symlink-creation concerns raised above.
Related requests from the MXC filesystem compatibility checks:
Keeping this issue as the existing tracker for exposing HostFs inside the sandbox workload rather than creating a duplicate.
lpcox commentedon Sep 30, 2026
Superseded by updated requirements for live-sharing host directories with NVX via virtio-fs in sandbox mode to match Cloud Hypervisor parity. A new issue will track the updated requirements.
NVX: live virtio-fs workspace share + guest-caller ownership (prototy…