summaryrefslogtreecommitdiffstats
path: root/src/unshare.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/unshare.rs')
-rw-r--r--src/unshare.rs130
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(())
+}