diff options
Diffstat (limited to 'src/unshare.rs')
-rw-r--r-- | src/unshare.rs | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/src/unshare.rs b/src/unshare.rs new file mode 100644 index 0000000..2aa2b09 --- /dev/null +++ b/src/unshare.rs @@ -0,0 +1,130 @@ +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_pid(s: &OsStr) -> Option<libc::uid_t> { + s.to_str().and_then(|s| s.parse::<libc::uid_t>().ok()) +} + +fn parse_id_range(line: Vec<u8>, uid: &OsStr, username: Option<&OsStr>) -> Option<SubIDRange> { + 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; + } + + if let (Some(start), Some(count)) = (parse_pid(parts[1]), parse_pid(parts[2])) { + Some(SubIDRange { start, count }) + } else { + None + } +} + +fn read_id_ranges(filename: &Path) -> Result<Vec<SubIDRange>> { + 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'); + + let mut ranges = Vec::new(); + + for line in lines { + if let Some(range) = parse_id_range(line?, &uidstr, usernamestr) { + ranges.push(range); + } + } + + Ok(ranges) +} + +#[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<SubIDRange>) -> Vec<SubIDMap> { + 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<Vec<SubIDMap>> { + 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<Vec<SubIDMap>> { + 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<SubIDMap>) -> Result<PreparedCommand> { + 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(()) +} |