diff --git a/Cargo.lock b/Cargo.lock index 60cbeb36..7b30089e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -641,6 +641,7 @@ version = "0.24.7" dependencies = [ "fs-err", "nix 0.27.1", + "rustix", "strum", "thiserror 2.0.3", ] @@ -2364,15 +2365,15 @@ checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2e487a7d..736105ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ petgraph = "0.6.5" rayon = "1.10.0" regex = "1.10.5" reqwest = { version = "0.12.5", default-features = false, features = ["brotli", "charset", "deflate", "gzip", "http2", "rustls-tls", "stream", "zstd"] } +rustix = { version = "0.38.42", features = ["mount"] } serde = { version = "1.0.204", features = ["derive"] } serde_json = "1.0.120" serde_yaml = "0.9.34" diff --git a/crates/container/Cargo.toml b/crates/container/Cargo.toml index bcf2b0dd..23702635 100644 --- a/crates/container/Cargo.toml +++ b/crates/container/Cargo.toml @@ -9,6 +9,7 @@ rust-version.workspace = true [dependencies] fs-err.workspace = true nix.workspace = true +rustix.workspace = true strum.workspace = true thiserror.workspace = true diff --git a/crates/container/src/lib.rs b/crates/container/src/lib.rs index 819c7b85..f3a773a7 100644 --- a/crates/container/src/lib.rs +++ b/crates/container/src/lib.rs @@ -11,7 +11,6 @@ use std::sync::atomic::{AtomicI32, Ordering}; use fs_err::{self as fs, PathExt as _}; use nix::libc::SIGCHLD; -use nix::mount::{mount, umount2, MntFlags, MsFlags}; use nix::sched::{clone, CloneFlags}; use nix::sys::prctl::set_pdeathsig; use nix::sys::signal::{kill, sigaction, SaFlags, SigAction, SigHandler, Signal}; @@ -19,6 +18,10 @@ use nix::sys::signalfd::SigSet; use nix::sys::stat::{umask, Mode}; use nix::sys::wait::{waitpid, WaitStatus}; use nix::unistd::{close, pipe, pivot_root, read, sethostname, tcsetpgrp, write, Pid, Uid}; +use rustix::{ + fs::MountPropagationFlags, + mount::{mount, mount_bind, mount_change, mount_remount, unmount, MountFlags, UnmountFlags}, +}; use thiserror::Error; use self::idmap::idmap; @@ -247,23 +250,29 @@ fn pivot(root: &Path, binds: &[Bind]) -> Result<(), ContainerError> { let old_root = root.join(OLD_PATH); - add_mount(None, "/", None, MsFlags::MS_REC | MsFlags::MS_PRIVATE)?; - add_mount(Some(root), root, None, MsFlags::MS_BIND)?; + mount_change("/", MountPropagationFlags::REC | MountPropagationFlags::PRIVATE) + .map_err(|source| ContainerError::MountSetPrivate { source })?; + mount_bind(root, root).map_err(|source| ContainerError::BindMountRoot { source })?; for bind in binds { let source = bind.source.fs_err_canonicalize()?; let target = root.join(bind.target.strip_prefix("/").unwrap_or(&bind.target)); - add_mount(Some(&source), &target, None, MsFlags::MS_BIND)?; + ensure_directory(&target)?; + mount_bind(&source, &target).map_err(|err| ContainerError::BindMount { + source: source.clone(), + target: target.clone(), + err, + })?; // Remount to enforce readonly flag if bind.read_only { - add_mount( - Some(source), - target, - None, - MsFlags::MS_BIND | MsFlags::MS_REMOUNT | MsFlags::MS_RDONLY, - )?; + mount_remount(&target, MountFlags::BIND | MountFlags::RDONLY, "").map_err(|source| { + ContainerError::MountSetReadOnly { + target: target.clone(), + source, + } + })?; } } @@ -272,22 +281,12 @@ fn pivot(root: &Path, binds: &[Bind]) -> Result<(), ContainerError> { set_current_dir("/")?; - add_mount(Some("proc"), "proc", Some("proc"), MsFlags::empty())?; - add_mount(Some("tmpfs"), "tmp", Some("tmpfs"), MsFlags::empty())?; - add_mount( - Some(format!("/{OLD_PATH}/sys").as_str()), - "sys", - None, - MsFlags::MS_BIND | MsFlags::MS_REC | MsFlags::MS_SLAVE, - )?; - add_mount( - Some(format!("/{OLD_PATH}/dev").as_str()), - "dev", - None, - MsFlags::MS_BIND | MsFlags::MS_REC | MsFlags::MS_SLAVE, - )?; - - umount2(OLD_PATH, MntFlags::MNT_DETACH).map_err(ContainerError::UnmountOldRoot)?; + add_mount("proc", "proc", "proc")?; + add_mount("tmpfs", "tmp", "tmpfs")?; + bind_mount_downstream(&format!("/{OLD_PATH}/sys"), "sys")?; + bind_mount_downstream(&format!("/{OLD_PATH}/dev"), "dev")?; + + unmount(OLD_PATH, UnmountFlags::DETACH).map_err(ContainerError::UnmountOldRoot)?; fs::remove_dir(OLD_PATH)?; umask(Mode::S_IWGRP | Mode::S_IWOTH); @@ -319,28 +318,33 @@ fn ensure_directory(path: impl AsRef) -> Result<(), ContainerError> { Ok(()) } -fn add_mount>( - source: Option, - target: T, - fs_type: Option<&str>, - flags: MsFlags, -) -> Result<(), ContainerError> { +fn add_mount(source: impl AsRef, target: impl AsRef, fs_type: &str) -> Result<(), ContainerError> { + let source = source.as_ref(); let target = target.as_ref(); ensure_directory(target)?; - mount( - source.as_ref().map(AsRef::as_ref), - target, - fs_type, - flags, - Option::<&str>::None, - ) - .map_err(|err| ContainerError::Mount { + mount(source, target, fs_type, MountFlags::empty(), "").map_err(|err| ContainerError::Mount { target: target.to_owned(), err, })?; Ok(()) } +fn bind_mount_downstream(source: &str, target: &str) -> Result<(), ContainerError> { + ensure_directory(target)?; + mount_bind(source, target).map_err(|err| ContainerError::BindMount { + source: source.into(), + target: target.into(), + err, + })?; + mount_change(target, MountPropagationFlags::REC | MountPropagationFlags::SLAVE).map_err(|source| { + ContainerError::MountSetDownstream { + target: target.into(), + source, + } + })?; + Ok(()) +} + fn set_current_dir(path: impl AsRef) -> io::Result<()> { #[derive(Debug, Error)] #[error("failed to set current directory to `{}`", path.display())] @@ -469,13 +473,28 @@ enum ContainerError { #[error("pivot_root")] PivotRoot(#[source] nix::Error), #[error("unmount old root")] - UnmountOldRoot(#[source] nix::Error), - #[error("mount {}", target.display())] + UnmountOldRoot(#[source] rustix::io::Errno), + #[error("failed to change existing mounts to private")] + MountSetPrivate { source: rustix::io::Errno }, + #[error("failed to rebind root")] + BindMountRoot { source: rustix::io::Errno }, + #[error("failed to bind-mount {source} to {target}")] + BindMount { + source: PathBuf, + target: PathBuf, + #[source] + err: rustix::io::Errno, + }, + #[error("failed to mount {}", target.display())] Mount { target: PathBuf, #[source] - err: nix::Error, + err: rustix::io::Errno, }, + #[error("failed to set mount to read-only")] + MountSetReadOnly { target: PathBuf, source: rustix::io::Errno }, + #[error("failed to set mount to downstream mode")] + MountSetDownstream { target: PathBuf, source: rustix::io::Errno }, } #[repr(u8)]