Skip to content

feat(vm): add host bind mount support via virtiofs (libkrun) - #3463

Open
benoitf wants to merge 1 commit into
NVIDIA:mainfrom
benoitf:feat/vm-bind-mounts
Open

benoitf wants to merge 1 commit into
NVIDIA:mainfrom
benoitf:feat/vm-bind-mounts

Conversation

@benoitf

@benoitf benoitf commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Add host directory bind mount support for libkrun VM sandboxes via the virtiofs4 API with simplified permission semantics. Operators enable the feature with enable_bind_mounts = true in [openshell.drivers.vm], and sandbox creators specify mounts in driver_config.mounts[].

note: it depends on a new release of kernel published at https://github.com/NVIDIA/OpenShell/actions/workflows/release-vm-kernel.yml for pre-built vm (with newer libkrun)

Related Issue

#2585

Changes

  • Gateway config (openshell-gateway): accept enable_bind_mounts in VmComputeConfig and forward it as --enable-bind-mounts to the VM driver subprocess.
  • FFI (openshell-driver-vm): add an optional krun_add_virtiofs4 binding, so older libkrun runtimes still boot sandboxes without mounts.
  • Runtime (openshell-driver-vm): new VmMount type and add_virtiofs() calling virtiofs4 with simplified semantics; QEMU refuses mounts.
  • Admission (openshell-driver-vm): driver_config.mounts[] takes source, target, optional read_only (default read-only) and optional type: bind, so Docker and Podman bind entries (without selinux_label) parse unchanged. Mounts require allow_driver_config, enable_bind_mounts and disabled resource admission. The driver rejects GPU and other QEMU launches, sources with control characters, reserved VM-internal targets, nested or duplicate targets, and more mounts than libkrun has interrupt lines for (32 on aarch64, 3 on x86_64). The QEMU check runs again after backend resolution.
  • Provisioning (openshell-driver-vm): host sources are checked before image preparation. Missing, inaccessible, symlinked, non-directory and unreadable sources are rejected. The driver generates virtiofs tags, always writes the guest mount manifest into the overlay (so an image-supplied manifest can never drive mounts), logs each shared directory, and forwards --vm-mount arguments that the launcher parses from the right.
  • Init script: mount_virtiofs_shares() mounts the shares after every /sandbox ownership fixup and the init drop-ins, refuses targets that traverse a symlink in the image, and skips an empty manifest.
  • Kernel config: list CONFIG_FUSE_FS and CONFIG_VIRTIO_FS explicitly; the pinned libkrunfw base config already enables both.
  • Docs: enable_bind_mounts in docs/how-it-works/gateways/configuration.mdx; a "MicroVM Mounts" section in docs/how-it-works/sandboxes/runtimes.mdx covering the opt-in, the mount limit, unsupported sources, the guest-kernel exposure, and the rootless Linux UID/GID note.
  • e2e: new vm_bind_mount test; the VM e2e gateway enables bind mounts like the Docker and Podman e2e gateways.

Known limits

  • At most 3 mounts on x86_64 and 32 on aarch64. libkrun has 11 virtio interrupt lines on x86_64 and the VM's own devices use up to 8 of them.
  • On Linux with a non-root gateway, the sandbox can create files in a read-write mount only when its UID/GID match the gateway user's (sandbox_uid / sandbox_gid).
  • libkrun does not confine a share to source against a compromised guest kernel; the launcher serves it as the gateway user. Confining the launcher (for example with Landlock) is not part of this change.

Testing

  • Unit tests for mount config deserialization (default read-only, explicit read-write, type: bind)
  • Unit tests for mount validation (requires enable_bind_mounts and disabled resource admission; rejects QEMU, relative sources, control characters, reserved paths, nested and duplicate targets, too many mounts)
  • Unit tests for host source checks (missing, inaccessible, unreadable, symlinked, file) and for running them before image resolution
  • Unit tests for prepare_virtiofs_target in the guest init script (creates directories, refuses symlinks and non-directories)
  • Unit test that recovery is denied once enable_bind_mounts is revoked
  • Unit tests for --vm-mount argument parsing and manifest rendering
  • cargo test -p openshell-driver-vm, cargo fmt --check and cargo clippy on the driver crate (macOS arm64)
  • vm_bind_mount e2e on Linux KVM (GitHub runner) and macOS HVF (my machine)
  • Boot with the maximum number of mounts on x86_64 and aarch64

Checklist

  • Conventional commit format
  • DCO sign-off
  • Docs updated (configuration.mdx, runtimes.mdx)
  • No secrets or credentials committed
  • mise run pre-commit passes on this revision
  • mise run test passes on this revision

@copy-pr-bot

copy-pr-bot Bot commented Sep 18, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@clholzin

Copy link
Copy Markdown

Confirming from the requester side: this matches what we asked for in #1509 — opt-in volume mounts for MicroVM sandboxes.

The core ask there was the ability to add virtiofs devices beyond the rootfs in a libkrun VM, behind an explicit operator opt-in, with the guest-side mount handled during the driver's boot sequence. That's what this implements. Thanks for picking it up.

@benoitf
benoitf force-pushed the feat/vm-bind-mounts branch from d7236e6 to 3c81133 Compare September 23, 2026 08:21
@benoitf

benoitf commented Sep 23, 2026

Copy link
Copy Markdown
Contributor Author

I rebased the PR on top of the main branch due to conflicts

@benoitf
benoitf force-pushed the feat/vm-bind-mounts branch 3 times, most recently from bd1d7b2 to 135f022 Compare September 28, 2026 08:13
@benoitf

benoitf commented Sep 28, 2026

Copy link
Copy Markdown
Contributor Author

rebased and added e2e tests

@benoitf
benoitf force-pushed the feat/vm-bind-mounts branch from 135f022 to 606a784 Compare October 1, 2026 08:14
@benoitf

benoitf commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

rebased to fix conflict

@benoitf
benoitf force-pushed the feat/vm-bind-mounts branch from 606a784 to 71ddc0f Compare October 5, 2026 17:47
@benoitf

benoitf commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

rebased

@sideDesert

sideDesert commented Oct 9, 2026 •

Copy link
Copy Markdown

when will this get merged? we are sort of stuck since we need to mount a host filepath for our usecase

@benoitf

benoitf commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

@sideDesert I'm waiting for a review 🚀

@elezar elezar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Requesting changes primarily because file-creation coverage and successful VM end-to-end runs on Linux KVM and macOS HVF are still missing. I found no confirmed implementation defect in the reviewed paths; this review does not require adding launcher confinement in this PR. The inline comments request clarification of the documented host exposure and validation of the file-creation workflow.

Please update the VM troubleshooting section in skills/debug-openshell-cluster/SKILL.md alongside this driver configuration change, as required by the repository instructions. Add checks for allow_driver_config, enable_bind_mounts, disabled resource admission, a runtime providing krun_add_virtiofs4, unsupported GPU/QEMU launches, and UID/GID mismatch when file creation fails on rootless Linux.

Comment thread docs/how-it-works/sandboxes/runtimes.mdx Outdated
Comment thread e2e/rust/tests/vm_bind_mount.rs
@benoitf
benoitf force-pushed the feat/vm-bind-mounts branch from 71ddc0f to 073f217 Compare October 13, 2026 07:33
Introduce host directory bind mounts for libkrun VM sandboxes using
the virtiofs4 API with simplified permission semantics. The gateway
accepts `enable_bind_mounts` in the `[openshell.drivers.vm]` TOML
table and forwards it to the VM driver subprocess.

Mounts are specified per-sandbox in driver_config.mounts[] with
source, target, optional read_only (default: read-only), and an
optional `type` that must be `bind`, so Docker and Podman bind entries
(without `selinux_label`) parse unchanged.

At sandbox validation the driver rejects mounts unless bind mounts
are enabled and resource admission is disabled. It rejects GPU and
other QEMU launches, sources with control characters, and more mounts
than libkrun has interrupt lines for (32 on aarch64, 3 on x86_64), and
checks targets against reserved VM-internal directories and each other
(no nested targets). The QEMU check runs again once the launch backend
is resolved.

Host source directories are checked at provisioning, before image
preparation, so a briefly missing source fails provisioning visibly
instead of denying recovery of a persisted sandbox. Missing,
inaccessible, symlinked, non-directory, and unreadable sources are
rejected: libkrun does not follow a final symlink, and on macOS it
panics during guest boot when it cannot open the share root.

The driver generates virtiofs tags and always writes a guest-side
manifest into the overlay disk, together with the init drop-ins, so an
image-supplied manifest can never drive guest mounts. The launcher
parses its tab-separated --vm-mount arguments from the right, so a
source can never shift the tag or mode, and the driver logs each
shared directory at launch.

Guest init mounts the shares after every /sandbox ownership fixup
and the init drop-ins, so recursive chowns never reach host files.
It refuses targets that traverse symlinks in the image, hands newly
created mount point parents under /sandbox to the sandbox user, and
skips an empty manifest.

krun_add_virtiofs4 is resolved optionally, so older libkrun runtimes
still boot sandboxes without mounts.

The kernel config lists CONFIG_FUSE_FS and CONFIG_VIRTIO_FS
explicitly; the pinned libkrunfw base config already enables both.

A vm_bind_mount e2e test checks a read-write share round-trips data
with the host, accepts a new file, directory, and nested file that the
host then sees, and that a read-only share rejects writes. libkrun on
rootless Linux refuses file creation by a guest user whose UID/GID
differ from its own, so the VM e2e gateway runs the sandbox as the
gateway user there. It enables bind mounts like the Docker and Podman
e2e gateways.

The runtimes docs cover the mount limit, the symlink restriction, the
guest-kernel exposure, and the rootless Linux UID/GID note. The
debug-openshell-cluster skill lists VM bind mount checks.

Signed-off-by: Florent Benoit <fbenoit@redhat.com>
@benoitf
benoitf force-pushed the feat/vm-bind-mounts branch from 073f217 to 73c9361 Compare October 13, 2026 07:34
@benoitf

benoitf commented Oct 13, 2026

Copy link
Copy Markdown
Contributor Author

@elezar I've updated my PR

Docs

  • Reworded the VM mount warning to state that libkrun does not confine a compromised guest kernel to the configured source directory; it may access other host files permitted by the gateway user's permissions.

E2E coverage (e2e/rust/tests/vm_bind_mount.rs)

  • The sandbox now creates a new file, a new directory, and a file inside that directory in the read-write share. The test verifies all three from the host, in addition to the existing write-through and read-only checks.
  • On rootless Linux, e2e/rust/e2e-vm.sh sets sandbox_uid/sandbox_gid to the gateway user's UID/GID (and passes --sandbox-uid/--sandbox-gid to the external driver), as documented.

Skill

  • Added VM bind mount checks to skills/debug-openshell-cluster/SKILL.md: allow_driver_config, enable_bind_mounts, disabled resource admission, a runtime providing krun_add_virtiofs4, unsupported GPU/QEMU launches, and UID/GID mismatch when file creation fails on rootless Linux.

Test runs against the current revision

I've launched e2e on my mac and setup a custom runner on GitHub for Linux KVM

Platform Task Result
macOS HVF (arm64) e2e:vm test vm_bind_mount ... ok
Linux KVM (ubuntu-24.04, x86_64, non-root) e2e:vm test vm_bind_mount ... ok
Linux KVM (ubuntu-24.04, x86_64, non-root) e2e:vm:external-driver test vm_bind_mount ... ok

you can find GitHub custom launch for Linux KVM run at https://github.com/benoitf/OpenShell/actions/runs/38360705661

@elezar elezar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The updated PR addresses all three requests from my previous review: the warning now explicitly describes the guest-kernel exposure beyond the configured source, the bind-mount e2e test covers creation of a file, directory, and nested file with host-side verification, and the VM troubleshooting skill includes the requested checks.

I verified passing Linux KVM vm_bind_mount runs for both the managed and external-driver paths. The runner commit differs from this PR head only by the custom CI workflow, so the tested source matches. The author also reports a passing macOS HVF run; I have not independently reproduced that run. No remaining blocking findings from this review.

Minor follow-up: please update the PR description's testing checklist, which still says the VM bind-mount e2e test has not been run.

@benoitf

benoitf commented Oct 13, 2026

Copy link
Copy Markdown
Contributor Author

Minor follow-up: please update the PR description's testing checklist, which still says the VM bind-mount e2e test has not been run.

I've updated the checkbox

@benoitf

benoitf commented Oct 13, 2026

Copy link
Copy Markdown
Contributor Author

@elezar could you add the ok to test instruction please ?

@TaylorMutch

Copy link
Copy Markdown
Collaborator

/ok-to-test 73c9361

@elezar elezar added the test:e2e Requires end-to-end coverage label Oct 14, 2026
@github-actions

Copy link
Copy Markdown

Label test:e2e applied for 73c9361. Open the existing run and click Re-run all jobs to execute with the label set. The run will execute the standard E2E suite after building the required gateway, sandbox, and supervisor images once. The matching required CI gate status on this PR will flip green automatically once the run finishes.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:e2e Requires end-to-end coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants