Repository navigation
nvx-rs: bind a resolver file into entries, and return removable runtime binds - #876
Merged
Pedro Henrique Penna (ppenna) merged 4 commits intoOct 27, 2026
Conversation
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Failed-bind rollback silently discards unmount errors, leaving unreported mounts that can block root teardown.
1 open issue
What changed in this PR
Implements the guest-library foundation for layered-root DNS in #860: optional resolver binds and explicit runtime-bind removal. Adapter integration and VM validation remain for PR 2.
Changes:
- Adds strict DNS-token parsing and resolver-file writing.
- Adds resolver binds and removable runtime-bind tracking.
- Reserves
/etc/resolv.confacross host and guest validation, with tests and documentation.
| File | Description |
|---|---|
nvx-rs/src/protocol/share.rs |
Reserves the resolver target and tests path boundaries. |
nvx-rs/src/protocol/mod.rs |
Documents DNS protocol helpers. |
nvx-rs/src/protocol/cmdline/tests.rs |
Tests DNS parsing and resolver output. |
nvx-rs/src/protocol/cmdline.rs |
Adds DNS parsing and resolver writing. |
nvx-rs/src/host/sandbox/tests.rs |
Tests resolver-target rejection. |
nvx-rs/src/host/sandbox/specification.rs |
Extends reserved-target property tests. |
nvx-rs/src/host/sandbox/plan/tests.rs |
Tests rejection in export plans. |
nvx-rs/src/host/sandbox/mount/tests.rs |
Tests reserved targets and allowed neighbors. |
nvx-rs/src/host/filesystem.rs |
Extends filesystem export validation tests. |
nvx-rs/src/guest/root/share.rs |
Tests guest share reservation boundaries. |
nvx-rs/src/guest/root.rs |
Documents the reserved resolver target. |
nvx-rs/src/guest/mappings/tests.rs |
Tests reverse unbinding and failure reporting. |
nvx-rs/src/guest/mappings/privileged.rs |
Adds privileged resolver-target rejection coverage. |
nvx-rs/src/guest/mappings.rs |
Tracks runtime binds and adds reverse unbinding. |
nvx-rs/src/guest/managed_agent/seam.rs |
Adapts bind results and resolver errors. |
nvx-rs/src/guest/managed_agent/dispatch.rs |
Tests rejection of resolver mappings. |
nvx-rs/src/guest/entry/tests.rs |
Tests resolver binds, rollback, flags, and teardown. |
nvx-rs/src/guest/entry.rs |
Adds resolver configuration and removable runtime binds. |
nvx-rs/README.md |
Documents the new library APIs. |
doc/usage.md |
Updates CLI reserved-target documentation. |
doc/run.md |
Explains resolver-target reservation. |
Review effort: Balanced
This was referenced Oct 25, 2026
Enrique Saurez (esaurez)
force-pushed
the
esaurez/860-pr1-resolver-runtime-binds-20261024
branch
from
October 27, 2026 01:00
1f17457 to
50f7fe1
Compare
Enrique Saurez (esaurez)
changed the base branch from
dev
to
esaurez/822-pr11-network-enforcement-vms-20261023
October 27, 2026 01:00
This was referenced Oct 27, 2026
This was referenced Oct 27, 2026
Pedro Henrique Penna (ppenna)
force-pushed
the
esaurez/822-pr11-network-enforcement-vms-20261023
branch
from
October 27, 2026 03:23
c453a9f to
bf1ff1e
Compare
Base automatically changed from
esaurez/822-pr11-network-enforcement-vms-20261023
to
dev
October 27, 2026 04:54
OpenVMM names the guest's DNS server, one of the gateway's addresses, with a virtnet_dns= token when the network policy lets the guest reach the gateway's DNS service, and NVX's init writes the initramfs's /etc/resolv.conf from it. Nothing reads it for a layered root, whose executions see the image's own file (#860). cmdline::dns_server reads the token as OpenVMM renders and reads it, strictly: the command line split at ASCII whitespace, as OpenVMM splits it, rather than as the kernel does, at most one token, and one IPv4 or IPv6 address in the form that OpenVMM writes. cmdline::write_resolver writes the resolver file that names it, with the content that the init writes, so that a root's executions and the initramfs agree. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 28b0282d-1872-4f0c-bc51-7d2a72ebd2db
A layered root's executions are to see the guest's resolver file over the root's /etc/resolv.conf, as they see a generated machine ID over its /etc/machine-id (#860). A live share or a host mapping there would cover that file, or be covered by it, and only /etc itself and /etc/machine-id are reserved today. Add /etc/resolv.conf to RESERVED_MOUNT_TARGETS, which the host's planner and the guest's mapper both refuse, so that a share at it, or below it, is refused before launch, and in the guest. A neighbor such as /etc/resolvconf stays free. Test it on the host, in the guest's checks, and with the kernel's mounts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 28b0282d-1872-4f0c-bc51-7d2a72ebd2db
A layered root's executions see the image's own /etc/resolv.conf, which an image may ship empty, so they resolve no names even where their network policy allows DNS. And Entry::bind_runtime leaves its binds in the caller's mount namespace with no way to remove them, so a caller that tears the root down in that namespace fails, since a teardown detaches no busy mount (#860). Entry::resolver names a resolver file, such as the one that the guest's init wrote from the command line's DNS server, and bind_runtime binds it over the root's /etc/resolv.conf, after the machine ID and the time status, read-only, nosuid, and nodev, through the same walk and attach. As at /etc/machine-id, a symbolic link at /etc/resolv.conf itself, which no bind can cover, is replaced with an empty file first. Without a resolver file, the image's own stays. bind_runtime now returns the RuntimeBinds that it made, which RuntimeBinds::remove unmounts in reverse order, attempting each and reporting every one that stays, so that a caller removes them before the root's teardown, which then succeeds. If a bind fails, the call removes the binds that it made before it. A caller whose mount namespace ends before the teardown, as the managed agent's does, drops them as before. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 28b0282d-1872-4f0c-bc51-7d2a72ebd2db
When a runtime bind fails, Entry::bind_runtime removes the binds that it made before it, but it discarded the failures of that removal. A bind that stayed mounted, such as a busy one, then went unreported, and the caller, which never received the binds, learned of it only when the root's teardown failed. Report it instead: if the removal fails, the call returns an Error::Rollback that holds the bind's error and the TeardownError of the binds that stay, and whose source is the bind's error. The managed agent maps it to the errno of the bind that failed, as before. The call still attempts every removal, whatever failed before. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 28b0282d-1872-4f0c-bc51-7d2a72ebd2db
Pedro Henrique Penna (ppenna)
force-pushed
the
esaurez/860-pr1-resolver-runtime-binds-20261024
branch
from
October 27, 2026 05:05
50f7fe1 to
f3e0317
Compare
Pedro Henrique Penna (ppenna)
deleted the
esaurez/860-pr1-resolver-runtime-binds-20261024
branch
October 27, 2026 13:03
This was referenced Oct 27, 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.

Rebased on
devatd70c0ba3, after #883 merged.Stack: #876 (this PR) → #871 → #873 → #874, on
dev. #863, #864, #865, #866, #867, #868, #869, #870, #872, #875, #878, #882, and #883 are merged.Part of #860: PR 1 of 2, the resolver bind, and removable runtime binds, in
nvx::guest. PR 2, DNS in the test adapter, builds on it.Why
A layered root's executions see the image's own
/etc/resolv.conf, which an image may ship empty, so they resolve no names even where their network policy allows DNS. AndEntry::bind_runtimeleaves its binds in the caller's mount namespace with no way to remove them, so a caller that tears the root down in that namespace fails: a root's teardown detaches no busy mount.What
nvx::protocol::cmdline):dns_serverreads OpenVMM'svirtnet_dns=token,DNS_TOKEN, strictly, as OpenVMM renders and reads it: the command line split at ASCII whitespace, as OpenVMM splits it, rather than as the kernel does; at most one token; one IPv4 or IPv6 address in the form that OpenVMM writes.write_resolverwrites the resolver file that names it,nameserver ADDRESS, with the content that NVX's init writes into the initramfs's/etc/resolv.conf.nvx::protocol::share):/etc/resolv.confjoinsRESERVED_MOUNT_TARGETS, which the host's planner and the guest's mapper both refuse, so a share at it or below it, allowed until now, is refused. A neighbor such as/etc/resolvconfstays free.nvx::guest::entry):Entry::resolver(path)names a resolver file, andEntry::bind_runtimebinds it read-only,nosuid, andnodevover the root's/etc/resolv.conf, after the machine ID and the time status, through the same walk and attach. As at/etc/machine-id, a symbolic link at/etc/resolv.confitself is replaced with an empty file first. Without a resolver file, the image's own stays. A failure isentry::Error::Resolver.bind_runtimereturnsRuntimeBinds, the binds that it made.RuntimeBinds::removeunmounts them in reverse order, attempting each and reporting every one that stays as amappings::TeardownError, so that a caller removes them before the root's teardown, which then succeeds. If a bind fails,bind_runtimeremoves the binds that it made before it, and returns anentry::Error::Rollback, which holds the bind's error and names every bind that stays mounted, if that removal fails.Compatibility
Entry::bind_runtimereturnsResult<RuntimeBinds, Error>instead ofResult<(), Error>, and the non-exhaustiveentry::Errorhas two new variants,ResolverandRollback.RuntimeBindsisn't#[must_use], and dropping it leaves the binds mounted, as before, so callers whose mount namespace ends before the teardown, the managed agent and the test adapter'sentrieshelper, are unchanged in behavior./etc/resolv.confbefore launch, and nvx-rs: run NVX's layered sandbox root from a Rust PID 1 on the guest library #817 replaces that agent.Validation
At this head,
f3e0317c, rebased ontodev'sd70c0ba3without conflicts:git range-diffshows each of the four commits unchanged, and the six commits of #866, #882, and #883 that this branch carried are dropped, sincedevholds them.check-nvx-rs(format, the three Clippy configurations, the tests with all, default, and no default features, the documentation, and MSRV 1.89), and the runtime adapter's protocol-only and host configurations: pass.check-aci-edge-sandboxesno longer exists, since aci_edge_sandboxes: retire the crate with its CI, commands, and docs #917 retired it.x86_64-unknown-linux-gnu, checked from Windows): Clippy on the library's three configurations and on the runtime adapter's guest and host configurations: pass.At the previous head,
50f7fe14, on #883'sc453a9f2:check-nvx-rs(format, both Clippy configurations, the tests with all, default, and no default features, the documentation, and MSRV 1.89), the runtime adapter's protocol-only and host configurations, andcheck-aci-edge-sandboxeswithout its agent bundling step: pass.check-nvx-rsandcheck-aci-edge-sandboxes, including the macOS check, the guest binaries, the runtime adapter's guest configuration, and the agent bundling: pass. Clippy and the library's tests also pass at each commit. One known flaky test that this PR doesn't touch,guest::sys::signal::tests::a_thread_that_blocks_sigpipe_keeps_only_a_signal_that_was_pending, failed once at the second commit and passed when rerun.guest::entry's privileged tests, which check the binds' flags, the links' replacement, and binds removed before a teardown that then succeeds, and the mapper's privileged tests: pass. With their expected resolver contents altered, both entry tests failed, so they run rather than skip. CI runs them without privileges, where they return early.dev's CI didn't run on it; once the PR targeteddev, CI passed at that head.