use crate::{oslib, util};
use std::ffi::CString;
use std::fs::{self, File};
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::str::FromStr;
use std::{error, fmt, io};
#[derive(Debug)]
pub enum Error {
BindMountProcSelfFd(io::Error),
BindMountSharedDir(io::Error),
ChdirOldRoot(io::Error),
ChdirNewRoot(io::Error),
Chroot(io::Error),
ChrootChdir(io::Error),
CleanMount(io::Error),
CreateTempDir(io::Error),
DropSupplementalGroups(io::Error),
Fork(io::Error),
GetSupplementalGroups(io::Error),
MountBind(io::Error),
MountOldRoot(io::Error),
MountProc(io::Error),
MountNewRoot(io::Error),
MountTarget(io::Error),
OpenMountinfo(io::Error),
OpenNewRoot(io::Error),
OpenOldRoot(io::Error),
OpenProcSelf(io::Error),
OpenProcSelfFd(io::Error),
PivotRoot(io::Error),
RmdirTempDir(io::Error),
UmountOldRoot(io::Error),
UmountTempDir(io::Error),
Unshare(io::Error),
WriteGidMap(io::Error),
WriteSetGroups(io::Error),
WriteUidMap(io::Error),
SandboxModeInvalidUID,
}
impl error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use self::Error::SandboxModeInvalidUID;
match self {
SandboxModeInvalidUID => {
write!(
f,
"sandbox mode 'chroot' can only be used by \
root (Use '--sandbox namespace' instead)"
)
}
_ => write!(f, "{:?}", self),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SandboxMode {
Namespace,
Chroot,
None,
}
impl FromStr for SandboxMode {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"namespace" => Ok(SandboxMode::Namespace),
"chroot" => Ok(SandboxMode::Chroot),
"none" => Ok(SandboxMode::None),
_ => Err("Unknown sandbox mode"),
}
}
}
pub struct Sandbox {
shared_dir: String,
proc_self_fd: Option<File>,
mountinfo_fd: Option<File>,
sandbox_mode: SandboxMode,
}
impl Sandbox {
pub fn new(shared_dir: String, sandbox_mode: SandboxMode) -> io::Result<Self> {
let shared_dir_rp = fs::canonicalize(shared_dir)?;
let shared_dir_rp_str = shared_dir_rp
.to_str()
.ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))?;
Ok(Sandbox {
shared_dir: shared_dir_rp_str.into(),
proc_self_fd: None,
mountinfo_fd: None,
sandbox_mode,
})
}
fn setup_mounts(&mut self) -> Result<(), Error> {
let c_proc_self = CString::new("/proc/self").unwrap();
let proc_self_raw = unsafe { libc::open(c_proc_self.as_ptr(), libc::O_PATH) };
if proc_self_raw < 0 {
return Err(Error::OpenProcSelf(std::io::Error::last_os_error()));
}
let proc_self = unsafe { File::from_raw_fd(proc_self_raw) };
oslib::mount(None, "/", None, libc::MS_SLAVE | libc::MS_REC).map_err(Error::CleanMount)?;
oslib::mount(
"proc".into(),
"/proc",
"proc".into(),
libc::MS_NODEV | libc::MS_NOEXEC | libc::MS_NOSUID | libc::MS_RELATIME,
)
.map_err(Error::MountProc)?;
oslib::mount("/proc/self/fd".into(), "/proc", None, libc::MS_BIND)
.map_err(Error::BindMountProcSelfFd)?;
let c_proc_dir = CString::new("/proc").unwrap();
let proc_self_fd = unsafe { libc::open(c_proc_dir.as_ptr(), libc::O_PATH) };
if proc_self_fd < 0 {
return Err(Error::OpenProcSelfFd(std::io::Error::last_os_error()));
}
self.proc_self_fd = Some(unsafe { File::from_raw_fd(proc_self_fd) });
oslib::mount(
self.shared_dir.as_str().into(),
self.shared_dir.as_str(),
None,
libc::MS_BIND | libc::MS_REC,
)
.map_err(Error::BindMountSharedDir)?;
let c_root_dir = CString::new("/").unwrap();
let oldroot_fd = unsafe {
libc::open(
c_root_dir.as_ptr(),
libc::O_DIRECTORY | libc::O_RDONLY | libc::O_CLOEXEC,
)
};
if oldroot_fd < 0 {
return Err(Error::OpenOldRoot(std::io::Error::last_os_error()));
}
let c_shared_dir = CString::new(self.shared_dir.clone()).unwrap();
let newroot_fd = unsafe {
libc::open(
c_shared_dir.as_ptr(),
libc::O_DIRECTORY | libc::O_RDONLY | libc::O_CLOEXEC,
)
};
if newroot_fd < 0 {
return Err(Error::OpenNewRoot(std::io::Error::last_os_error()));
}
oslib::fchdir(newroot_fd).map_err(Error::ChdirNewRoot)?;
let c_current_dir = CString::new(".").unwrap();
let ret = unsafe {
libc::syscall(
libc::SYS_pivot_root,
c_current_dir.as_ptr(),
c_current_dir.as_ptr(),
)
};
if ret < 0 {
return Err(Error::PivotRoot(std::io::Error::last_os_error()));
}
oslib::fchdir(oldroot_fd).map_err(Error::ChdirOldRoot)?;
oslib::mount(None, ".", None, libc::MS_SLAVE | libc::MS_REC).map_err(Error::CleanMount)?;
oslib::umount2(".", libc::MNT_DETACH).map_err(Error::UmountOldRoot)?;
oslib::fchdir(newroot_fd).map_err(Error::ChdirNewRoot)?;
unsafe { libc::close(newroot_fd) };
unsafe { libc::close(oldroot_fd) };
let c_mountinfo = CString::new("mountinfo").unwrap();
let mountinfo_fd =
unsafe { libc::openat(proc_self.as_raw_fd(), c_mountinfo.as_ptr(), libc::O_RDONLY) };
if mountinfo_fd < 0 {
return Err(Error::OpenMountinfo(std::io::Error::last_os_error()));
}
self.mountinfo_fd = Some(unsafe { File::from_raw_fd(mountinfo_fd) });
Ok(())
}
fn setup_id_mappings(&self, uid: u32, gid: u32) -> Result<(), Error> {
fs::write("/proc/self/setgroups", "deny\n").map_err(Error::WriteSetGroups)?;
let uid_mapping = format!("{} {} 1\n", uid, uid);
fs::write("/proc/self/uid_map", uid_mapping).map_err(Error::WriteUidMap)?;
let gid_mapping = format!("{} {} 1\n", gid, gid);
fs::write("/proc/self/gid_map", gid_mapping).map_err(Error::WriteGidMap)?;
Ok(())
}
pub fn enter_namespace(&mut self) -> Result<(), Error> {
let uid = unsafe { libc::geteuid() };
let gid = unsafe { libc::getegid() };
let flags = if uid == 0 {
libc::CLONE_NEWPID | libc::CLONE_NEWNS | libc::CLONE_NEWNET
} else {
libc::CLONE_NEWPID | libc::CLONE_NEWNS | libc::CLONE_NEWNET | libc::CLONE_NEWUSER
};
let ret = unsafe { libc::unshare(flags) };
if ret != 0 {
return Err(Error::Unshare(std::io::Error::last_os_error()));
}
let child = util::sfork().map_err(Error::Fork)?;
if child == 0 {
if uid != 0 {
self.setup_id_mappings(uid, gid)?;
}
self.setup_mounts()?;
Ok(())
} else {
util::wait_for_child(child); }
}
pub fn enter_chroot(&mut self) -> Result<(), Error> {
let c_proc_self_fd = CString::new("/proc/self/fd").unwrap();
let proc_self_fd = unsafe { libc::open(c_proc_self_fd.as_ptr(), libc::O_PATH) };
if proc_self_fd < 0 {
return Err(Error::OpenProcSelfFd(std::io::Error::last_os_error()));
}
self.proc_self_fd = Some(unsafe { File::from_raw_fd(proc_self_fd) });
let c_mountinfo = CString::new("/proc/self/mountinfo").unwrap();
let mountinfo_fd = unsafe { libc::open(c_mountinfo.as_ptr(), libc::O_RDONLY) };
if mountinfo_fd < 0 {
return Err(Error::OpenMountinfo(std::io::Error::last_os_error()));
}
self.mountinfo_fd = Some(unsafe { File::from_raw_fd(mountinfo_fd) });
let c_shared_dir = CString::new(self.shared_dir.clone()).unwrap();
let ret = unsafe { libc::chroot(c_shared_dir.as_ptr()) };
if ret != 0 {
return Err(Error::Chroot(std::io::Error::last_os_error()));
}
let c_root_dir = CString::new("/").unwrap();
let ret = unsafe { libc::chdir(c_root_dir.as_ptr()) };
if ret != 0 {
return Err(Error::ChrootChdir(std::io::Error::last_os_error()));
}
Ok(())
}
fn drop_supplemental_groups(&self) -> Result<(), Error> {
let ngroups = unsafe { libc::getgroups(0, std::ptr::null_mut()) };
if ngroups < 0 {
return Err(Error::GetSupplementalGroups(std::io::Error::last_os_error()));
} else if ngroups != 0 {
let ret = unsafe { libc::setgroups(0, std::ptr::null()) };
if ret != 0 {
return Err(Error::DropSupplementalGroups(
std::io::Error::last_os_error(),
));
}
}
Ok(())
}
pub fn enter(&mut self) -> Result<(), Error> {
let uid = unsafe { libc::geteuid() };
if uid != 0 && self.sandbox_mode == SandboxMode::Chroot {
return Err(Error::SandboxModeInvalidUID);
}
if uid == 0 {
self.drop_supplemental_groups()?;
}
match self.sandbox_mode {
SandboxMode::Namespace => self.enter_namespace(),
SandboxMode::Chroot => self.enter_chroot(),
SandboxMode::None => Ok(()),
}
}
pub fn get_proc_self_fd(&mut self) -> Option<File> {
self.proc_self_fd.take()
}
pub fn get_mountinfo_fd(&mut self) -> Option<File> {
self.mountinfo_fd.take()
}
pub fn get_root_dir(&self) -> String {
match self.sandbox_mode {
SandboxMode::Namespace | SandboxMode::Chroot => "/".to_string(),
SandboxMode::None => self.shared_dir.clone(),
}
}
pub fn get_mountinfo_prefix(&self) -> Option<String> {
match self.sandbox_mode {
SandboxMode::Namespace | SandboxMode::None => None,
SandboxMode::Chroot => Some(self.shared_dir.clone()),
}
}
}