use std::{ ffi::{OsStr, OsString}, fs::File, io::{self, BufRead, Result}, os::unix::ffi::*, path::Path, }; use nix::{ sched::{self, CloneFlags}, unistd, }; use crate::prepared_command::PreparedCommand; use crate::util::{Checkable, ToIOResult}; #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] struct SubIDRange { start: libc::uid_t, count: libc::uid_t, } fn parse_uid(s: &OsStr) -> Option { s.to_str().and_then(|s| s.parse().ok()) } fn parse_id_range(line: Vec, uid: &OsStr, username: Option<&OsStr>) -> Option { let parts: Vec<_> = line.split(|c| *c == b':').map(OsStr::from_bytes).collect(); if parts.len() != 3 { return None; } if parts[0] != uid && Some(parts[0]) != username { return None; } let start = parse_uid(parts[1])?; let count = parse_uid(parts[2])?; Some(SubIDRange { start, count }) } fn read_id_ranges(filename: &Path) -> Result> { let uid = users::get_effective_uid(); let username = users::get_user_by_uid(uid).map(|user| user.name().to_os_string()); let uidstr = OsString::from(uid.to_string()); let usernamestr = username.as_ref().map(OsString::as_os_str); let file = File::open(filename)?; let lines = io::BufReader::new(file).split(b'\n'); lines .map(|line| Ok(parse_id_range(line?, &uidstr, usernamestr))) .filter_map(Result::transpose) .collect() } #[derive(Debug)] struct SubIDMap { lower: libc::uid_t, upper: libc::uid_t, count: libc::uid_t, } fn generate_idmap(id: libc::uid_t, mut ranges: Vec) -> Vec { let mut map = Vec::new(); map.push(SubIDMap { lower: 0, upper: id, count: 1, }); let mut lower = 1; ranges.sort(); for range in &ranges { map.push(SubIDMap { lower, upper: range.start, count: range.count, }); lower += range.count; } map } fn get_uid_map() -> Result> { let uid = users::get_effective_uid(); let uid_ranges = read_id_ranges(Path::new("/etc/subuid"))?; Ok(generate_idmap(uid, uid_ranges)) } fn get_gid_map() -> Result> { let gid = users::get_effective_gid(); let gid_ranges = read_id_ranges(Path::new("/etc/subgid"))?; Ok(generate_idmap(gid, gid_ranges)) } fn prepare_idmap_cmd(cmd: &str, pid: &str, map: &Vec) -> Result { let mut builder = PreparedCommand::new(cmd); builder.arg(&pid); for uids in map { builder.arg(uids.lower.to_string()); builder.arg(uids.upper.to_string()); builder.arg(uids.count.to_string()); } builder.prepare() } pub fn unshare() -> Result<()> { let pid = unistd::getpid().to_string(); let newuidmap = prepare_idmap_cmd("newuidmap", pid.as_str(), &get_uid_map()?)?; let newgidmap = prepare_idmap_cmd("newgidmap", pid.as_str(), &get_gid_map()?)?; sched::unshare(CloneFlags::CLONE_NEWUSER | CloneFlags::CLONE_NEWNS).to_io_result()?; newuidmap.run()?.check()?; newgidmap.run()?.check()?; Ok(()) }