summaryrefslogtreecommitdiffstats
path: root/src/unshare.rs
blob: a8ec1c163e7eb42a9fba5a4f293e44c9e3d810e4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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<libc::uid_t> {
	s.to_str().and_then(|s| s.parse().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;
	}

	let start = parse_uid(parts[1])?;
	let count = parse_uid(parts[2])?;

	Some(SubIDRange { start, count })
}

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');

	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<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(())
}