Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port root pivoting code from nix to rustix #389

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions crates/container/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
105 changes: 62 additions & 43 deletions crates/container/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@ 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};
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;
Expand Down Expand Up @@ -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 })?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: if you use newtype enum pattern MountSetPrivate(InnerError) you can do

.map_err(ContainerError::MountSetPrivate)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know. I was following the snafu pattern already basically. Do you think I should change it for more consistency with what we already have, for now?

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,
}
})?;
}
}

Expand All @@ -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);
Expand Down Expand Up @@ -319,28 +318,33 @@ fn ensure_directory(path: impl AsRef<Path>) -> Result<(), ContainerError> {
Ok(())
}

fn add_mount<T: AsRef<Path>>(
source: Option<T>,
target: T,
fs_type: Option<&str>,
flags: MsFlags,
) -> Result<(), ContainerError> {
fn add_mount(source: impl AsRef<Path>, target: impl AsRef<Path>, 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<Path>) -> io::Result<()> {
#[derive(Debug, Error)]
#[error("failed to set current directory to `{}`", path.display())]
Expand Down Expand Up @@ -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)]
Expand Down
Loading