pub(crate) fn check_clone_new_user_errno(error: i32) -> i32 {
match error {
libc::EPERM | libc::EUSERS | libc::EINVAL | libc::ENOSPC => {
tracing::debug!(
errno = error,
message = %std::io::Error::from_raw_os_error(error),
"clone(CLONE_NEWUSER) failed with expected errno"
);
}
_ => {
tracing::error!(
errno = error,
message = %std::io::Error::from_raw_os_error(error),
"clone(CLONE_NEWUSER) failed with UNEXPECTED errno"
);
}
}
error
}
pub(crate) fn can_create_process_in_new_user_ns() -> Result<(), i32> {
unsafe {
let uid = libc::getuid();
let gid = libc::getgid();
let pid = libc::syscall(
libc::SYS_clone,
libc::CLONE_NEWUSER | libc::SIGCHLD,
std::ptr::null::<libc::c_void>(), std::ptr::null::<libc::c_void>(), std::ptr::null::<libc::c_void>(), 0i64, ) as libc::pid_t;
if pid == -1 {
let errno = *libc::__errno_location();
return Err(check_clone_new_user_errno(errno));
}
if pid == 0 {
if write_proc_file("/proc/self/setgroups\0", b"deny").is_err() {
libc::_exit(1);
}
let mut gid_buf = [0u8; 32];
let gid_len = format_id_map(&mut gid_buf, gid, gid);
if write_proc_file("/proc/self/gid_map\0", &gid_buf[..gid_len]).is_err() {
libc::_exit(1);
}
let mut uid_buf = [0u8; 32];
let uid_len = format_id_map(&mut uid_buf, uid, uid);
if write_proc_file("/proc/self/uid_map\0", &uid_buf[..uid_len]).is_err() {
libc::_exit(1);
}
if libc::unshare(libc::CLONE_NEWUSER) != 0 {
libc::_exit(1);
}
libc::_exit(0);
}
let mut status: libc::c_int = -1;
loop {
let r = libc::waitpid(pid, &mut status, 0);
if r == pid {
break;
}
if r == -1 && *libc::__errno_location() != libc::EINTR {
return Err(*libc::__errno_location());
}
}
if libc::WIFEXITED(status) && libc::WEXITSTATUS(status) == 0 {
Ok(())
} else {
Err(libc::EPERM)
}
}
}
fn format_id_map(buf: &mut [u8; 32], inside_id: libc::uid_t, outside_id: libc::uid_t) -> usize {
let mut pos = 0;
pos += write_u32_to_buf(&mut buf[pos..], inside_id);
buf[pos] = b' ';
pos += 1;
pos += write_u32_to_buf(&mut buf[pos..], outside_id);
buf[pos] = b' ';
pos += 1;
buf[pos] = b'1';
pos += 1;
buf[pos] = b'\n';
pos += 1;
pos
}
fn write_u32_to_buf(buf: &mut [u8], mut n: u32) -> usize {
if n == 0 {
buf[0] = b'0';
return 1;
}
let mut temp = [0u8; 10]; let mut len = 0;
while n > 0 {
temp[len] = b'0' + (n % 10) as u8;
n /= 10;
len += 1;
}
for i in 0..len {
buf[i] = temp[len - 1 - i];
}
len
}
unsafe fn write_proc_file(path: &str, content: &[u8]) -> Result<(), ()> {
unsafe {
let fd = libc::open(
path.as_ptr() as *const libc::c_char,
libc::O_WRONLY | libc::O_CLOEXEC,
);
if fd < 0 {
return Err(());
}
let written = libc::write(fd, content.as_ptr() as *const libc::c_void, content.len());
libc::close(fd);
if written < 0 { Err(()) } else { Ok(()) }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_id_map() {
let mut buf = [0u8; 32];
let len = format_id_map(&mut buf, 1000, 1000);
assert_eq!(&buf[..len], b"1000 1000 1\n");
}
#[test]
fn test_format_id_map_zero() {
let mut buf = [0u8; 32];
let len = format_id_map(&mut buf, 0, 0);
assert_eq!(&buf[..len], b"0 0 1\n");
}
#[test]
fn test_write_u32_to_buf() {
let mut buf = [0u8; 16];
let len = write_u32_to_buf(&mut buf, 12345);
assert_eq!(&buf[..len], b"12345");
}
#[test]
fn test_write_u32_to_buf_zero() {
let mut buf = [0u8; 16];
let len = write_u32_to_buf(&mut buf, 0);
assert_eq!(&buf[..len], b"0");
}
#[test]
fn test_check_clone_new_user_errno_expected() {
assert_eq!(check_clone_new_user_errno(libc::EPERM), libc::EPERM);
assert_eq!(check_clone_new_user_errno(libc::EUSERS), libc::EUSERS);
assert_eq!(check_clone_new_user_errno(libc::EINVAL), libc::EINVAL);
assert_eq!(check_clone_new_user_errno(libc::ENOSPC), libc::ENOSPC);
}
#[test]
fn test_check_clone_new_user_errno_unexpected() {
assert_eq!(check_clone_new_user_errno(libc::EACCES), libc::EACCES);
}
#[test]
fn test_can_create_process_in_new_user_ns() {
let result = can_create_process_in_new_user_ns();
match result {
Ok(()) => {
}
Err(errno) => {
assert!(
errno == libc::EPERM
|| errno == libc::EUSERS
|| errno == libc::EINVAL
|| errno == libc::ENOSPC,
"Unexpected errno: {} ({})",
errno,
std::io::Error::from_raw_os_error(errno)
);
}
}
}
}