use std::fs;
use std::io::Write;
use std::path::Path;
use crate::error::{VettoError, VettoResult};
pub const CLONE_NEWNS: libc::c_int = 0x0002_0000;
pub const CLONE_NEWUSER: libc::c_int = 0x1000_0000;
pub const CLONE_NEWPID: libc::c_int = 0x2000_0000;
pub const CLONE_NEWNET: libc::c_int = 0x4000_0000;
pub const CLONE_NEWIPC: libc::c_int = 0x0800_0000;
pub fn unshare(flags: libc::c_int) -> VettoResult<()> {
let r = unsafe { libc::unshare(flags) };
if r != 0 {
return Err(VettoError::Namespace(format!(
"unshare({flags:#x}) failed: {}",
std::io::Error::last_os_error()
)));
}
Ok(())
}
fn proc_sys_u32(path: &str) -> Option<u64> {
fs::read_to_string(path).ok()?.trim().parse().ok()
}
pub fn userns_knobs_look_enabled() -> bool {
let clone_ok = match proc_sys_u32("/proc/sys/kernel/unprivileged_userns_clone") {
Some(v) => v != 0,
None => true, };
let max_ok = proc_sys_u32("/proc/sys/user/max_user_namespaces").unwrap_or(u64::MAX) > 0;
clone_ok && max_ok
}
pub fn probe_unprivileged_userns() -> bool {
probe_userns(false)
}
pub fn probe_full_tier() -> bool {
probe_userns(true)
}
fn probe_userns(require_full_stack: bool) -> bool {
if !userns_knobs_look_enabled() {
return false;
}
let mut ready_fds = [0 as libc::c_int; 2]; let mut ack_fds = [0 as libc::c_int; 2]; if unsafe { libc::pipe2(ready_fds.as_mut_ptr(), libc::O_CLOEXEC) } != 0 {
return false;
}
if unsafe { libc::pipe2(ack_fds.as_mut_ptr(), libc::O_CLOEXEC) } != 0 {
return false;
}
match unsafe { libc::fork() } {
-1 => false,
0 => {
unsafe {
libc::close(ready_fds[0]);
libc::close(ack_fds[1]);
}
let entered = unshare(CLONE_NEWUSER).is_ok();
let byte: &[u8] = if entered { &[1] } else { &[0] };
let _ = unsafe { libc::write(ready_fds[1], byte.as_ptr().cast(), 1) };
if !entered {
unsafe { libc::_exit(1) };
}
let mut ack = [0u8; 1];
let n = unsafe { libc::read(ack_fds[0], ack.as_mut_ptr().cast(), 1) };
unsafe {
libc::close(ready_fds[1]);
libc::close(ack_fds[0]);
}
let mapped = n == 1 && ack[0] == 0;
let ready = mapped && (!require_full_stack || probe_full_stack_in_child());
unsafe { libc::_exit(if ready { 0 } else { 1 }) };
}
pid => {
let mut status = 0i32;
parent_probe_side(pid, ready_fds, ack_fds, &mut status)
}
}
}
fn probe_full_stack_in_child() -> bool {
if unshare(CLONE_NEWNS).is_err() || super::mounts::make_root_private().is_err() {
return false;
}
if super::mounts::isolate_dev_shm().is_err()
|| unshare(CLONE_NEWIPC).is_err()
|| unshare(CLONE_NEWNET).is_err()
|| unshare(CLONE_NEWPID).is_err()
{
return false;
}
let pid = unsafe { libc::fork() };
if pid < 0 {
return false;
}
if pid == 0 {
let ok = super::mounts::mount_restricted_proc().is_ok();
unsafe { libc::_exit(if ok { 0 } else { 1 }) };
}
let mut status = 0;
loop {
let result = unsafe { libc::waitpid(pid, &mut status, 0) };
if result == pid {
break;
}
if result < 0 && std::io::Error::last_os_error().raw_os_error() != Some(libc::EINTR) {
return false;
}
}
libc::WIFEXITED(status) && libc::WEXITSTATUS(status) == 0
}
fn parent_probe_side(
pid: libc::pid_t,
ready_fds: [libc::c_int; 2],
ack_fds: [libc::c_int; 2],
status: &mut i32,
) -> bool {
unsafe {
libc::close(ready_fds[1]);
libc::close(ack_fds[0]);
}
let mut ready = [0u8; 1];
let n = unsafe { libc::read(ready_fds[0], ready.as_mut_ptr().cast(), 1) };
let entered = n == 1 && ready[0] == 1;
let mut maps_ok = false;
if entered {
maps_ok = write_id_maps(pid).is_ok();
let byte: &[u8] = if maps_ok { &[0] } else { &[1] };
let _ = unsafe { libc::write(ack_fds[1], byte.as_ptr().cast(), 1) };
}
unsafe {
libc::close(ready_fds[0]);
libc::close(ack_fds[1]);
}
loop {
let r = unsafe { libc::waitpid(pid, status, 0) };
if r == pid
|| (r < 0 && std::io::Error::last_os_error().raw_os_error() != Some(libc::EINTR))
{
break;
}
}
let exited_zero = libc::WIFEXITED(*status) && libc::WEXITSTATUS(*status) == 0;
entered && maps_ok && exited_zero
}
pub fn write_id_maps(pid: libc::pid_t) -> VettoResult<()> {
let base = Path::new("/proc").join(pid.to_string());
let uid = unsafe { libc::getuid() }; let gid = unsafe { libc::getgid() };
let _ = fs::write(base.join("setgroups"), "deny");
let w = |name: &str, content: String| -> VettoResult<()> {
let path = base.join(name);
let mut f = fs::OpenOptions::new()
.write(true)
.open(&path)
.map_err(|e| VettoError::Namespace(format!("open {}: {e}", path.display())))?;
f.write_all(content.as_bytes())
.map_err(|e| VettoError::Namespace(format!("write {}: {e}", path.display())))?;
Ok(())
};
w("uid_map", format!("0 {uid} 1\n"))?;
w("gid_map", format!("0 {gid} 1\n"))?;
Ok(())
}