Skip to content

Commit 3715df4

Browse files
ammarioclaude
andcommitted
fix: prevent DNS bind-mount from escaping namespace and corrupting host
## Problem The fallback DNS setup code in `ensure_namespace_dns()` was using bind mounts inside network namespaces that could escape namespace isolation and corrupt the host system's DNS configuration. The issue: `ip netns exec` only enters the **network** namespace, NOT the mount namespace. When the code attempted to bind-mount over `/etc/resolv.conf` (which is a symlink to `/run/systemd/resolve/stub-resolv.conf`), the kernel followed the symlink in the **host's mount namespace** and created a bind mount that corrupted the host's DNS. This caused DNS resolution to fail system-wide on ci-1, breaking the GitHub Actions runner for 3 weeks. Evidence from ci-1: - 165+ orphaned namespace configs in /etc/netns/ - Multiple bind mounts on /run/systemd/resolve/stub-resolv.conf - Host's stub-resolv.conf contained namespace DNS content ## Solution Removed the dangerous bind-mount fallback code (lines 540-577) and replaced it with a safe approach that only updates `/etc/netns/<name>/resolv.conf`, which is automatically bind-mounted by the kernel when the namespace is created. The new fallback: - Updates the /etc/netns/ file directly (safe, host filesystem) - Adds extensive documentation explaining why bind mounts are unsafe - Fails gracefully with warnings if DNS setup fails ## Testing - Verified DNS resolution works: test_jail_dns_resolution passes - Verified no bind mounts created on stub-resolv.conf - All Linux integration tests pass on ml-1 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 574ca02 commit 3715df4

1 file changed

Lines changed: 26 additions & 57 deletions

File tree

src/jail/linux/mod.rs

Lines changed: 26 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -520,63 +520,32 @@ nameserver {}\n",
520520
std::fs::write(&temp_resolv, &dns_content)
521521
.with_context(|| format!("Failed to create temp resolv.conf: {}", temp_resolv))?;
522522

523-
// First, try to directly write to /etc/resolv.conf in the namespace using echo
524-
let write_cmd = Command::new("ip")
525-
.args([
526-
"netns",
527-
"exec",
528-
&namespace_name,
529-
"sh",
530-
"-c",
531-
&format!("echo 'nameserver {}' > /etc/resolv.conf", host_ip),
532-
])
533-
.output();
534-
535-
if let Ok(output) = write_cmd {
536-
if !output.status.success() {
537-
let stderr = String::from_utf8_lossy(&output.stderr);
538-
warn!("Failed to write resolv.conf into namespace: {}", stderr);
539-
540-
// Try another approach - mount bind
541-
let mount_cmd = Command::new("ip")
542-
.args([
543-
"netns",
544-
"exec",
545-
&namespace_name,
546-
"mount",
547-
"--bind",
548-
&temp_resolv,
549-
"/etc/resolv.conf",
550-
])
551-
.output();
552-
553-
if let Ok(mount_output) = mount_cmd {
554-
if mount_output.status.success() {
555-
info!("Successfully bind-mounted resolv.conf in namespace");
556-
} else {
557-
let mount_stderr = String::from_utf8_lossy(&mount_output.stderr);
558-
warn!("Failed to bind mount resolv.conf: {}", mount_stderr);
559-
560-
// Last resort - try copying the file content
561-
let cp_cmd = Command::new("cp")
562-
.args([
563-
&temp_resolv,
564-
&format!(
565-
"/proc/self/root/etc/netns/{}/resolv.conf",
566-
namespace_name
567-
),
568-
])
569-
.output();
570-
571-
if let Ok(cp_output) = cp_cmd
572-
&& cp_output.status.success()
573-
{
574-
info!("Successfully copied resolv.conf via /proc");
575-
}
576-
}
577-
}
578-
} else {
579-
info!("Successfully wrote resolv.conf into namespace");
523+
// SAFE FALLBACK: Update the /etc/netns/<name>/resolv.conf file
524+
// This avoids dangerous operations inside the namespace that could escape isolation.
525+
//
526+
// IMPORTANT: We do NOT use bind mounts inside namespaces because:
527+
// 1. `ip netns exec` only enters the network namespace, NOT the mount namespace
528+
// 2. Bind mounting /etc/resolv.conf (which is a symlink) in the host mount namespace
529+
// will follow the symlink and corrupt /run/systemd/resolve/stub-resolv.conf on the HOST
530+
// 3. This breaks DNS for the entire system, not just the namespace
531+
//
532+
// Instead, we update /etc/netns/<name>/resolv.conf which should have been automatically
533+
// bind-mounted by the kernel when the namespace was created.
534+
let netns_resolv_path = format!("/etc/netns/{}/resolv.conf", namespace_name);
535+
536+
match std::fs::write(&netns_resolv_path, &dns_content) {
537+
Ok(_) => {
538+
info!(
539+
"Updated namespace-specific resolv.conf at {}",
540+
netns_resolv_path
541+
);
542+
}
543+
Err(e) => {
544+
warn!(
545+
"Failed to update {}: {}. DNS may not work in namespace. \
546+
This is safe but the namespace will not have working DNS.",
547+
netns_resolv_path, e
548+
);
580549
}
581550
}
582551

0 commit comments

Comments
 (0)