use anyhow::{Context, Result};
use log::debug;
use nix::sys::wait::waitpid;
use nix::unistd::{ForkResult, Gid, Pid, Uid, fork};
use std::fs::File;
use std::io::Write;
use std::os::unix::io::AsRawFd;
pub(super) struct IdmapHelper {
_pid: Pid,
userns_fd: File,
}
impl IdmapHelper {
pub(super) fn new(
inner_uid: Uid,
outer_uid: Uid,
inner_gid: Gid,
outer_gid: Gid,
) -> Result<Self> {
let (read_fd, write_fd) = nix::unistd::pipe().context("failed to create sync pipe")?;
match unsafe { fork() }.context("failed to fork idmap helper")? {
ForkResult::Parent { child } => {
drop(write_fd);
let mut buf = [0u8; 1];
let bytes_read =
nix::unistd::read(&read_fd, &mut buf).context("failed to read from helper")?;
if bytes_read != 1 {
anyhow::bail!(
"helper failed during setup (read {} bytes, expected 1)",
bytes_read
);
}
drop(read_fd);
let userns_path = format!("/proc/{}/ns/user", child);
let userns_fd = File::open(&userns_path)
.with_context(|| format!("failed to open {}", userns_path))?;
debug!(
"Created idmap helper (PID {}) mapping {}:{} -> {}:{}",
child, inner_uid, inner_gid, outer_uid, outer_gid
);
Ok(IdmapHelper {
_pid: child,
userns_fd,
})
}
ForkResult::Child => {
drop(read_fd);
if let Err(e) = Self::setup_userns(inner_uid, outer_uid, inner_gid, outer_gid) {
eprintln!("idmap helper failed: {:?}", e);
unsafe { libc::_exit(1) };
}
let mut write_file = File::from(write_fd);
if let Err(e) = write_file.write_all(b"R") {
eprintln!("idmap helper failed to signal parent: {:?}", e);
unsafe { libc::_exit(1) };
}
loop {
std::thread::sleep(std::time::Duration::from_secs(3600));
}
}
}
}
fn setup_userns(inner_uid: Uid, outer_uid: Uid, inner_gid: Gid, outer_gid: Gid) -> Result<()> {
use nix::sched::{CloneFlags, unshare};
unshare(CloneFlags::CLONE_NEWUSER).context("failed to unshare user namespace")?;
std::fs::write("/proc/self/setgroups", b"deny").ok();
let uid_map = format!("{} {} 1\n", inner_uid, outer_uid);
std::fs::write("/proc/self/uid_map", uid_map.as_bytes())
.context("failed to write uid_map")?;
let gid_map = format!("{} {} 1\n", inner_gid, outer_gid);
std::fs::write("/proc/self/gid_map", gid_map.as_bytes())
.context("failed to write gid_map")?;
Ok(())
}
pub(super) fn userns_fd(&self) -> std::os::unix::io::BorrowedFd<'_> {
unsafe { std::os::unix::io::BorrowedFd::borrow_raw(self.userns_fd.as_raw_fd()) }
}
}
impl Drop for IdmapHelper {
fn drop(&mut self) {
use nix::sys::signal::{Signal, kill};
kill(self._pid, Signal::SIGKILL).ok();
waitpid(self._pid, None).ok();
}
}