Repository navigation
[Bubblewrap] Resolve DNS when the host uses a loopback stub resolver - #1453
Open
Soham Das (SohamDas2021) wants to merge 2 commits into
Open
Soham Das (SohamDas2021) wants to merge 2 commits into
Soham Das (SohamDas2021) wants to merge 2 commits into
Conversation
… resolves through a loopback stub Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b7decf7b-a659-437e-a056-d3269346bb89
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b7decf7b-a659-437e-a056-d3269346bb89
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Resolver fallback can abort or fail silently on non-UTF-8 paths, and the UDP-only firewall guidance omits required TCP fallback.
2 open findings
What changed in this PR
Fixes Bubblewrap DNS resolution when hosts use loopback stub resolvers by routing sandbox DNS through slirp.
Changes:
- Adds loopback resolver detection, pinning, and firewall diagnostics.
- Extracts shared symlink resolution logic.
- Adds documentation, unit tests, and an end-to-end DNS test.
| File | Description |
|---|---|
tests/scripts/run_bwrap_dns_test.sh |
Tests loopback-stub DNS resolution. |
tests/scripts/run_bwrap_all_tests.sh |
Registers the DNS test. |
tests/configs/bubblewrap_network_dns_stub.json |
Provides the E2E policy fixture. |
src/mxc-sdk/src/core/mxc_common/mod.rs |
Exposes the symlink helper. |
src/mxc-sdk/src/core/mxc_common/filesystem_symlink.rs |
Implements shared symlink resolution. |
src/mxc-sdk/src/backends/bubblewrap/common/proxy_network.rs |
Detects, stages, and mounts resolver pins. |
src/mxc-sdk/src/backends/bubblewrap/common/network_rules.rs |
Evaluates DNS-forwarder firewall admission. |
src/mxc-sdk/src/backends/bubblewrap/common/bwrap_runner.rs |
Integrates resolver pinning and warnings. |
src/mxc-sdk/src/backends/bubblewrap/common/bwrap_command.rs |
Clarifies resolver mount behavior. |
docs/backends/bwrap/bubblewrap-backend.md |
Documents loopback resolver handling. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| resolver: Option<&proxy_network::ResolverPin>, | ||
| logger: &mut Logger, | ||
| ) { | ||
| if resolver.is_none() || plan.admits_udp(proxy_network::SLIRP_DNS_FORWARDER_IP, DNS_PORT) { |
| } | ||
| }; | ||
|
|
||
| let destination_text = destination.to_str()?.to_string(); |
This branch has not been deployed
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.

📖 Description
On hosts that resolve through a loopback nameserver —
systemd-resolved's127.0.0.53(the default on Ubuntu and Fedora), a localdnsmasq, Pi-hole — the Bubblewrap sandbox could not resolve any hostname. The sandbox runs in its own network namespace, so that address is its own empty loopback rather than the host's resolver. Connections to numeric addresses kept working, which is why every existing network test passed.Under address filtering, the sandbox is now pointed at slirp's built-in DNS forwarder
10.0.2.3. libslirp rewrites a query sent there to the host's real nameserver and sends it from the host's network namespace, where the loopback resolver is reachable.search,options, and every other directive are carried over unchanged.The replacement only applies when the host's own resolvers are all loopback and at least one is IPv4, so a host that resolves today is untouched, and it never applies when libslirp would have no IPv4 nameserver to forward to. The generated file is mounted over the path the
/etc/resolv.confsymlink chain ends at, resolved through every component — bwrap cannot create a mount point beneath an unresolved symlink and aborts the sandbox when asked to.The pin never costs you a sandbox. It improves on a resolver the sandbox already cannot reach, so every failure gives up the pin rather than the run: an unreadable resolver, a chain that cannot be resolved, a path the filesystem policy denies, and a failure to write the replacement all leave the sandbox exactly as it would have started without this feature, each with a warning naming the cause.
Proxy mode and ruleless-deny are deliberately unchanged: a proxied chain opens no port 53 and the proxy resolves, and an isolated sandbox has no connectivity to resolve with.
Limitations
egress.default: "deny"with rules, the chain still governs: a query to10.0.2.3is dropped unless a rule admits it. This was already true before this change. MXC now warns at launch when it pins the resolver and the chain admits nothing to10.0.2.3:53, naming the rule to add rather than opening the port on the caller's behalf.-d 10.0.2.2/32 -j DROPdoes not cover it. Name resolution is the one host-loopback service an address-filtered sandbox can reach. Documented under Loopback resolvers.🔗 References
Resolves github/copilot-cli#5027
🔍 Validation
New E2E suite
tests/scripts/run_bwrap_dns_test.sh, registered inrun_bwrap_all_tests.sh. It builds the loopback-resolver condition rather than requiring it — re-executing in a private user/mount/network namespace, serving DNS on127.0.0.53, and pointing the resolver at it — so it behaves the same on a developer box and on a runner. It asserts a hostname resolves inside the sandbox, with a host-side control first so a sandbox failure is attributable. Verified it catches the bug: exit 1 against a binary built without the fix, exit 0 with it.The reported failure and the two sandbox-aborting regressions found while reviewing it were each reproduced against the real
lxc-execbefore and after the fix.src/,cargo fmt --all -- --check: passed.cargo clippy -p mxc-sdk --all-targets -- -D warnings: passed on Windows andx86_64-unknown-linux-gnu.cargo test -p mxc-sdk --lib: 1981 passed. Two failures inmxc_ptyandsdk_v1_conformancereproduce identically at this branch's merge-base and are unrelated.cargo test -p mxc-sdk --lib bubblewrap::common: 342 passed.deniedPathsmasking need passwordless sudo, which this host does not provide; CI covers them.✅ Checklist
Cargo.lock, thedependency-feed-checkcheck passes📋 Issue Type
Microsoft Reviewers: Open in CodeFlow