Repository navigation
guest: mount the runtime's file systems in a layered root - #1276
Pedro Henrique Penna (ppenna) merged 1 commit into
Conversation
This pull request changes existing testsIt modifies or removes 4 existing test asset(s), listed below. Review each with care: a change to a test can hide a regression that the test caught. See doc/contribute.md#changes-to-existing-tests. Test code
Test policy
Relocated unchanged: 0; added: 55. Compared the merge base |
There was a problem hiding this comment.
🟡 Changes recommended
A repeated mount attempt applies nodev to existing device mounts before returning Error::Occupied, leaving the previously working runtime unable to access device nodes such as /dev/null.
1 open issue
What changed in this PR
Adds one-time runtime filesystem setup to the guest’s layered root, preparing for nvx-init under #1104 while reusing the existing per-execution mount helpers.
Changes:
- Adds runtime mounts, optional read-only time-status binding, rollback, and teardown tracking.
- Shares filesystem definitions and device creation helpers between entry and root setup.
- Adds fake-kernel and privileged tests, documentation, and hidden API snapshot entries.
| File | Description |
|---|---|
| scripts/test-layout-allowlist.json | Records the temporary dead-code expectation’s test hook. |
| nvx-rs/src/guest/sys/mod.rs | Exposes shared runtime filesystem helpers internally. |
| nvx-rs/src/guest/sys/entry.rs | Extracts filesystem definitions and reusable mount and device helpers. |
| nvx-rs/src/guest/root/tests/runtime.rs | Tests mount ordering, failures, rollback, and teardown. |
| nvx-rs/src/guest/root/tests/privileged.rs | Checks real mount options, devices, bindings, and cleanup. |
| nvx-rs/src/guest/root/tests.rs | Registers the new test modules. |
| nvx-rs/src/guest/root/share.rs | Makes the mount-point opener available to root setup. |
| nvx-rs/src/guest/root/kernel/tests.rs | Extends the fake kernel with runtime mount operations. |
| nvx-rs/src/guest/root/kernel.rs | Adds kernel operations for mounts, restrictions, and devices. |
| nvx-rs/src/guest/root.rs | Implements runtime setup, rollback, tracking, and errors. |
| nvx-rs/src/guest/entry.rs | Shares the time-status target and binding helper internally. |
| nvx-rs/README.md | Documents runtime setup and privileged test commands. |
| nvx-rs/public-api/linux.txt | Records the new hidden runtime mount and error types. |
Review effort: Balanced
There was a problem hiding this comment.
🟡 Changes recommended
Mounting the /run tmpfs hides supported shares such as /run/app and breaks teardown, while mounting it earlier still causes mapper ownership errors; coordinated mount handling and regression coverage are needed.
1 open issue
1 resolved since last review
Review effort: Balanced
83da90b to
6c6b40f
Compare
1fe929c to
6be531e
Compare
6be531e to
423fed0
Compare
There was a problem hiding this comment.
🟡 Changes recommended
A critical rename race between target lookup and mount attachment can mount filesystems at unintended paths and leave them mounted after rollback and teardown.
1 open issue
1 resolved since last review
Review effort: Balanced
Part of #1104. Add the root's one-time runtime filesystem setup using the existing entry mount primitives. Restrict the root tree before mounting proc, read-only sysfs, devices, devpts, shared memory and run, optionally bind the time status read-only, and unwind a partial setup. Require runtime setup before live shares or mappings so that the run tmpfs cannot hide them. Register that tmpfs with the persistent mapper so later targets beneath it can be created and removed before the runtime mounts. Keep the entry point crate-private until init calls it, preserve existing per-execution behavior, and cover mount ordering, refusal and rollback with fake-kernel and privileged tests. Verify every runtime attachment before recording its path. Retain the mount descriptor when its post-attach check or time-status lookup and detach fail, so rollback and teardown can retry cleanup after a rename without losing ownership of the mount. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1930f155-69ef-41b6-9495-323b84ac76f7
423fed0 to
e4d4bca
Compare
d8f1e45
into
feature/nvx-refactor


Part of #1104: the space mounts its root's runtime file systems. Builds on the merged #1262.
What changes
Root::mount_runtime(time_status), which is crate-private, andRuntimeMounts, in the hiddenguest::root, give an init the one-time runtime setup that it makes before it switches into its root:nosuidandnodev. It then mounts, in order, procfs at/proc, sysfs read-only at/sys, a tmpfs at/devwith an execution's device nodes and links, devpts at/dev/pts, a tmpfs at/dev/shm, and a tmpfs at/run, each with the options with which an execution's entry mounts it;/run, which an entry does not mount, isnosuidandnodev, with mode 0755.nosuidandnodevat/run/nvx/time, asEntry::bind_runtimedoes. Without one, it binds nothing there.openat2, through no symbolic link or magic link and without crossing a mount, and creates a missing one with mode 0755, less what the umask withholds. A mount point where something is mounted already is refused asError::Occupied. So is a call while the root holds a runtime mount, before it opens or restricts anything, since restricting the tree again would make an earlier call's/devand/dev/ptsnodev: a second call changes nothing, and neither does a call after one whose undo left a mount for the teardown./runover an existing/run/appwould hide it from the caller and the teardown. Runtime setup comes first. The persistent mapper recognizes the runtime-owned/runtmpfs, so later shares and mappings beneath it can create their targets there and are unmounted before the tmpfs.RuntimeMountsnames the fixed, ordered runtime mounts, andMount::Runtimenames one in errors.No code calls
mount_runtimeyet: nvx-init, which #1115 adds, will. As #1076 asks of crate-private code whose caller comes in a later PR, the method is crate-private, with#[cfg_attr(not(test), expect(dead_code, reason = "nvx-init calls it (#1115)"))], rather than public, even hidden.RuntimeMounts::target, which only the root's own code uses, is crate-private too.RuntimeMountsstays public, since the publicErrornames a runtime mount throughMount::Runtime. The public docs name the method without a link, so that the documentation of hidden items links to no private item. The expectation's gate namestest, so the test layout counts it as a test hook, which its allowlist records forroot.rs.When nvx-init calls
mount_runtime, #1115 removes the expectation and the test hook that the allowlist records for it.The file systems come from
guest::sys::entry, which now names each one's type, parameters and attributes once, as aFileSystemconstant, for the entry and the root. The root mounts them through the entry's ownfsopen,fsconfigandfsmounthelpers, and creates the device nodes with the entry's code, then gives them mode 0666, since a root, unlike an entry's child, keeps its caller's umask. The per-execution path is unchanged in behavior: it still opens its targets fromAT_FDCWDthrough the same helpers, with the same flags and in the same order. The root's kernel seam gains the new mount, the tree's restriction and the device nodes, which the fake kernel records.Size
361 core lines in 7 counted files, measured against
256ebe3d, all undernvx-rs/src/guest/:root.rs,sys/entry.rs,root/kernel.rs,sys/mod.rs,entry.rs,root/share.rsandmappings/mapper.rs. This remains under the issue's 500-line stop. The seventh file preserves the existing mapper attach error and its surviving descriptor without changing the public bind API ormappings.rs. The allowlist's one new line, inscripts/test-layout-allowlist.json, is in a kind of file that #1076 does not count; counting it too gives 362 core lines in 8 files.nvx-rs/public-api/linux.txtgains 12 lines, all hidden:RuntimeMountsand its 7 variants,Mount::RuntimeandError::Restrict, each with its field. The crate-privateRoot::mount_runtimeandRuntimeMounts::targetare not in it.Tests
New tests:
guest::root::tests::runtime, 13 tests with the fake kernel: the restriction, then the mounts in order with their options, then the time status's bind, and the teardown's order; no bind without a time status; a failure at each of the 7 mounts, which unmounts the earlier ones; a failed restriction, which mounts nothing, and failed device nodes or devpts mount, which undo the call; a symbolic link at a mount point, and a second call, refused before it reaches the kernel; an undo whose unmount fails, which leaves that mount for the teardown, and a later call, refused the same way. The/runregressions cover refusal after an existing share, child or mapping, later shares and mappings on/runwith mapper-first teardown, and a failed/runownership check or an undo that leaves/runmounted. Two regressions cover an attached time-status bind whose lookup and detach fail: cleanup after its target moves, refusal of repeated setup without kernel calls, and reporting a persistent detach failure during teardown. Two more rename targets duringmove_mount: every runtime filesystem is verified and detached before registration; failed detaches on/dev,/dev/ptsand/runare retained, block repeated setup, and are either retried successfully or reported with the right mount and errno at teardown.guest::root::tests::privileged, 4 ignored tests that need root and Linux 6.8 or later and mount on a tmpfs root in their thread's private mount namespace: the options, modes, device nodes under a umask of 077, links, the read-only time status and the teardown; a call that fails part of the way, and a second call, after which every mount keeps its options and/dev/nullis still writable; refusal after an existing mapping, which remains reachable; and mappings beneath an already-mounted runtime/run, which stay reachable and are unmounted before it.Every existing test is kept.
check_test_changes.pyreports 4 changes, hencetests-modified: 3 to existing test helpers, and 1 to the test layout's allowlist:guest::root::kernel::tests::OperationgainsNewMount, andCallgainsNewMount,RestrictTreeandCreateDevices, for the seam's new operations.open_treecreates its tree's memory file throughtree_file, which new mounts share; it behaves as before.scripts/test-layout-allowlist.jsonallowsnvx-rs/src/guest/root.rsone test hook, the expectation onmount_runtime.By name, the kept tests of the changed code are the 64 of
guest::root::tests, the 11 of the root'sshare,blockandbootstrapmodules, the 31 ofguest::entryandguest::sys::entry, whose helpers the root now shares, and the 33 ofguest::mappings::mapper, whose test module shares the root's fake kernel. Their bodies are unchanged; none is removed or renamed.The 139 kept tests
Validation
Current head
e4d4bca7is one logical commit ondevbase256ebe3d, with tree9cbdbd90087f449d0b4e58dea28b042c28843d72and OpenVMM pin9cec2b89. The rebase had no conflicts. Older-head results are not counted as qualification of this corrected head.e4d4bca7: feature-set Clippy, default/all/no-default tests, real artifact bundling withNVX_BUNDLEunset, warning-denied documentation, MSRV, native runtime-adapter configurations, OpenVMM vectors/profiles and release CLI builds. Linux also passes both public-API snapshots, the macOS check, guest-binary lint/build/tests and the adapter's guest-only configuration.666ab612429947d84add4c11441ef5c5cf9421e45bb1a72f8dda7a07dcb205b4. The same guest-image bytes and static adapter guest are staged for both backends and checksum-verified.PATH: the full unit command reports 1810 tests, 67 skipped, and the previously shell-gated canceled-capture case executes; adversarial reports 84 tests, 11 skipped. Linux's full unit command reports 1810 tests, 30 skipped; adversarial reports all 84 tests with no skips. Both host-resolver runs report 4 tests, and the Linux-only Specula command reports all 25 tests. Every counted command has its runner's own successful summary; failed earlier environment attempts are retained separately.managed-exec-config,sandbox-lifecycle,sandbox-proxy-only,sandbox-ram-upper,private-network-ingressandtime-abi-conformancepass on both WHP and MSHV, using their default processor selections. The complete canonical adapter suite passes on each: 156 scenarios across 11 targets, with every runner reporting its expected positive count and no ignored cases. Test executables were compiled and checksum-verified before the guest-only phase; no compilation occurred during VM execution.v0.1.0-dev.256ebe3dd9c2, preserving that producer identity separately from the candidate's CLI, guest binaries and images. The kernel SHA-256 is692ac2a9f351aa5a31dcf1810782d82c1a83a0f1fd0b3edcf4fad9c0d94cfe81; the OpenVMM source pin is9cec2b89. Provenance validates against this checkout, and consumed bytes and the source head remain unchanged through both complete VM runs.Complete local qualification and independent source review apply to this exact head. The automatic GitHub Copilot review of the published head is handled separately.
The original WHP CI failure was a listener timeout in
vm_layered::an_allow_rule_lets_only_its_destination_through. That run checked out synthetic merge03e2a8fe, not the source head alone. The individual case and the complete 43-casevm_layeredtarget subsequently passed locally both on source head6be531ecand on that exact synthetic merge using its CI-produced guest and OpenVMM artifacts. The timeout has only been observed on the CI runner; its underlying cause is unproven, so this PR makes no speculative networking change. The separate advisory NFV run failed before producing findings because of a missingcontext_inputimport; no workflow change is included.Retained RED evidence is explicitly older-head evidence: the second-call tests fail at
f99a0c12with the later tests applied, including a real/dev/nullwrite refused after the repeated restriction. Atddf76e61with the later tests applied but without the correction, the three fake-kernel and two privileged/runregressions fail. The new moved-bind regression also failed on the unchanged production implementation before the descriptor-retention fix: the mount still existed after teardown.Both filesystem-rename regressions fail against
423fed09production code: procfs setup reports success while its moved mount remains after teardown, and/devloses the descriptor needed to refuse repeated setup and clean up its moved mount. They pass with the attachment verification and typed descriptor retention.KVM runtime checks remain for CI; no KVM hardware result is claimed here.