unshare: mount proc differently depending on root privileges

This commit is contained in:
2026-03-19 11:26:10 +01:00
parent 2b6207981a
commit 7640952bdc
2 changed files with 71 additions and 6 deletions

View File

@@ -207,10 +207,21 @@ impl UnshareDriver {
cmd.arg("-w").arg(dir);
}
cmd.arg("--").arg("bash").arg("-c").arg(format!(
"mount -t proc proc /proc; mkdir /dev/pts; mount -t devpts devpts /dev/pts; touch /dev/ptmx; mount --bind /dev/pts/ptmx /dev/ptmx; {} {}",
// Build the bash command: set up /dev/pts and run the program
// /proc should already be bind-mounted from the host before entering the namespace
let program_args = args
.iter()
.map(|a| format!("\"{a}\""))
.collect::<Vec<_>>()
.join(" ");
cmd.arg("--")
.arg("bash")
.arg("-c")
.arg(format!(
"mkdir -p /dev/pts; mount -t devpts devpts /dev/pts 2>/dev/null || true; touch /dev/ptmx; mount --bind /dev/pts/ptmx /dev/ptmx 2>/dev/null || true; {} {}",
program,
args.iter().map(|a| format!("\"{a}\"")).collect::<Vec<_>>().join(" ")
program_args
));
cmd

View File

@@ -134,7 +134,12 @@ impl EphemeralContextGuard {
// Create device nodes in the chroot
log::debug!("Creating device nodes in chroot...");
Self::create_device_nodes(chroot_path, ctx_for_devices)?;
Self::create_device_nodes(chroot_path, ctx_for_devices.clone())?;
// Bind mount /proc from host into chroot (before entering unshare namespace)
// This allows /proc to work in containers where mounting inside unshare fails
log::debug!("Bind-mounting /proc into chroot...");
Self::bind_mount_proc(chroot_path, ctx_for_devices)?;
Ok(())
}
@@ -286,6 +291,43 @@ impl EphemeralContextGuard {
Ok(())
}
/// Bind mount /proc from host into the chroot
/// This is done before entering the unshare namespace, so it works in containers
fn bind_mount_proc(
chroot_path: &Path,
ctx: Arc<context::Context>,
) -> Result<(), Box<dyn Error>> {
let proc_path = chroot_path.join("proc");
// Ensure /proc directory exists in chroot
fs::create_dir_all(&proc_path)?;
// Check if we're running as root
let is_root = crate::utils::root::is_root()?;
// Bind mount host's /proc into chroot (with sudo if not root)
let mut cmd = ctx.command(if is_root { "mount" } else { "sudo" });
if !is_root {
cmd.arg("mount");
}
let status = cmd
.arg("--bind")
.arg("/proc")
.arg(proc_path.to_string_lossy().to_string())
.status()?;
if !status.success() {
log::warn!(
"Could not bind-mount /proc into chroot at {}. Some packages may not install correctly.",
proc_path.display()
);
} else {
log::debug!("Bind-mounted /proc into chroot at {}", proc_path.display());
}
Ok(())
}
/// Mark the build as successful, which will trigger chroot cleanup on drop
pub fn mark_build_successful(&mut self) {
self.build_succeeded = true;
@@ -310,6 +352,18 @@ impl Drop for EphemeralContextGuard {
// Check if we're running as root to avoid unnecessary sudo
let is_root = crate::utils::root::is_root().unwrap_or(false);
// Unmount /proc from chroot before removing (ignore errors)
let proc_path = self.chroot_path.join("proc");
let _ = if is_root {
self.base_ctx.command("umount").arg(&proc_path).status()
} else {
self.base_ctx
.command("sudo")
.arg("umount")
.arg(&proc_path)
.status()
};
let result = if is_root {
self.base_ctx
.command("rm")