1use std::ffi::CString;
2use std::fmt::Debug;
3use std::fs::File;
4use std::io::{BufRead, BufReader};
5use std::panic::{catch_unwind, RefUnwindSafe, UnwindSafe};
6use std::process::Command;
7use std::str::FromStr;
8
9use nix::libc::uid_t;
10use nix::unistd::{getgid, getgrouplist, getuid, setgid, setgroups, setuid, User};
11
12use crate::{
13 clone3, exit_child, new_pipe, read_ok, read_result, write_ok, write_result, CloneArgs,
14 CloneResult, Error, OwnedPid, Pid,
15};
16
17pub type Uid = nix::unistd::Uid;
18pub type Gid = nix::unistd::Gid;
19
20#[derive(Clone, Debug)]
22pub struct IdMap<T> {
23 pub container_id: T,
25 pub host_id: T,
27 pub size: u32,
29}
30
31impl<T: From<uid_t>> IdMap<T> {
32 pub fn new_root(host_id: T) -> Self {
34 Self {
35 host_id,
36 container_id: 0.into(),
37 size: 1,
38 }
39 }
40}
41
42pub trait UserMapper: Send + Sync + Debug + RefUnwindSafe {
44 fn run_map_user(&self, pid: Pid) -> Result<(), Error>;
46
47 fn set_user(&self, uid: Uid, gid: Gid) -> Result<(), Error>;
49
50 fn is_uid_mapped(&self, id: Uid) -> bool;
52
53 fn is_gid_mapped(&self, id: Gid) -> bool;
55
56 fn uid_count(&self) -> u32;
58
59 fn gid_count(&self) -> u32;
61}
62
63#[derive(Clone, Debug)]
64pub struct ProcUserMapper {
65 pub uid_map: Vec<IdMap<Uid>>,
66 pub gid_map: Vec<IdMap<Gid>>,
67 pub set_groups: bool,
68}
69
70impl ProcUserMapper {
71 pub fn new_root(uid: Uid, gid: Gid) -> Self {
73 Self {
74 uid_map: vec![IdMap::new_root(uid)],
75 gid_map: vec![IdMap::new_root(gid)],
76 set_groups: false,
77 }
78 }
79}
80
81impl Default for ProcUserMapper {
83 fn default() -> Self {
84 Self::new_root(getuid(), getgid())
85 }
86}
87
88impl UserMapper for ProcUserMapper {
89 fn run_map_user(&self, _pid: Pid) -> Result<(), Error> {
91 todo!()
92 }
93
94 fn set_user(&self, uid: Uid, gid: Gid) -> Result<(), Error> {
96 if self.set_groups {
97 let groups = match User::from_uid(uid)? {
98 Some(user) => getgrouplist(&CString::new(user.name.as_bytes())?, gid)?,
99 None => vec![gid],
100 };
101 setgroups(&groups)?;
102 }
103 setgid(gid)?;
104 Ok(setuid(uid)?)
105 }
106
107 fn is_uid_mapped(&self, uid: Uid) -> bool {
109 is_id_mapped(&self.uid_map, uid)
110 }
111
112 fn is_gid_mapped(&self, gid: Gid) -> bool {
114 is_id_mapped(&self.gid_map, gid)
115 }
116
117 fn uid_count(&self) -> u32 {
119 self.uid_map.iter().fold(0, |acc, x| acc + x.size)
120 }
121
122 fn gid_count(&self) -> u32 {
124 self.gid_map.iter().fold(0, |acc, x| acc + x.size)
125 }
126}
127
128#[derive(Clone, Debug)]
134pub struct BinNewIdMapper {
135 pub uid_map: Vec<IdMap<Uid>>,
136 pub gid_map: Vec<IdMap<Gid>>,
137 pub uid_binary: String,
138 pub gid_binary: String,
139}
140
141impl BinNewIdMapper {
142 pub fn new_root(uid: Uid, gid: Gid) -> Self {
148 Self {
149 uid_map: vec![IdMap::new_root(uid)],
150 gid_map: vec![IdMap::new_root(gid)],
151 uid_binary: "/bin/newuidmap".to_owned(),
152 gid_binary: "/bin/newgidmap".to_owned(),
153 }
154 }
155
156 pub fn new_root_subid(uid: Uid, gid: Gid) -> Result<Self, Error> {
162 let user = match User::from_uid(uid)? {
163 Some(v) => v,
164 None => return Err(format!("Unknown user: {uid}").into()),
165 };
166 Ok(Self {
167 uid_map: Self::get_id_subid_map("/etc/subuid", uid, &user)?,
168 gid_map: Self::get_id_subid_map("/etc/subgid", gid, &user)?,
169 uid_binary: "/bin/newuidmap".to_owned(),
170 gid_binary: "/bin/newgidmap".to_owned(),
171 })
172 }
173
174 fn get_id_subid_map<T>(path: &str, id: T, user: &User) -> Result<Vec<IdMap<T>>, Error>
175 where
176 T: Copy + From<uid_t> + Into<uid_t>,
177 {
178 Ok(match Self::find_subid(path, user)? {
179 Some(v) => vec![
180 IdMap::new_root(id),
181 IdMap {
182 container_id: 1.into(),
183 host_id: v.0,
184 size: v.1,
185 },
186 ],
187 None => vec![IdMap::new_root(id)],
188 })
189 }
190
191 fn find_subid<T>(path: &str, user: &User) -> Result<Option<(T, u32)>, Error>
192 where
193 T: From<uid_t>,
194 {
195 let file = BufReader::new(File::open(path)?);
196 for line in file.lines() {
197 let line = line?;
198 let parts: Vec<_> = line.split(':').collect();
199 if parts.len() >= 3 && parts[0] == user.name {
200 let start = uid_t::from_str(parts[1])?;
201 let size = u32::from_str(parts[2])?;
202 return Ok(Some((start.into(), size)));
203 }
204 }
205 Ok(None)
206 }
207
208 fn run_id_map<T>(id_map: &[IdMap<T>], binary: &str, pid: Pid) -> Result<(), Error>
209 where
210 T: Copy + Into<uid_t>,
211 {
212 let mut cmd = Command::new(binary);
213 cmd.arg(pid.as_raw().to_string());
214 for v in id_map {
215 cmd.arg(v.container_id.into().to_string())
216 .arg(v.host_id.into().to_string())
217 .arg(v.size.to_string());
218 }
219 let mut child = cmd.spawn()?;
220 let status = child.wait()?;
221 if !status.success() {
222 let code = status.code().unwrap_or(0);
223 return Err(format!("{binary} exited with code {code}").into());
224 }
225 Ok(())
226 }
227}
228
229impl Default for BinNewIdMapper {
231 fn default() -> Self {
232 Self::new_root(getuid(), getgid())
233 }
234}
235
236impl UserMapper for BinNewIdMapper {
237 fn run_map_user(&self, pid: Pid) -> Result<(), Error> {
239 Self::run_id_map(&self.uid_map, &self.uid_binary, pid)
240 .map_err(|v| format!("Cannot map users: {v}"))?;
241 Self::run_id_map(&self.gid_map, &self.gid_binary, pid)
242 .map_err(|v| format!("Cannot map groups: {v}"))?;
243 Ok(())
244 }
245
246 fn set_user(&self, uid: Uid, gid: Gid) -> Result<(), Error> {
248 let groups = match User::from_uid(uid)? {
249 Some(user) => getgrouplist(&CString::new(user.name.as_bytes())?, gid)?,
250 None => vec![gid],
251 };
252 setgroups(&groups).map_err(|v| format!("Cannot set groups: {v}"))?;
253 setgid(gid).map_err(|v| format!("Cannot set group: {v}"))?;
254 Ok(setuid(uid).map_err(|v| format!("Cannot set user: {v}"))?)
255 }
256
257 fn is_uid_mapped(&self, uid: Uid) -> bool {
259 is_id_mapped(&self.uid_map, uid)
260 }
261
262 fn is_gid_mapped(&self, gid: Gid) -> bool {
264 is_id_mapped(&self.gid_map, gid)
265 }
266
267 fn uid_count(&self) -> u32 {
269 self.uid_map.iter().fold(0, |acc, x| acc + x.size)
270 }
271
272 fn gid_count(&self) -> u32 {
274 self.gid_map.iter().fold(0, |acc, x| acc + x.size)
275 }
276}
277
278pub fn run_as_user<
279 T: UserMapper + RefUnwindSafe + ?Sized,
280 Fn: FnOnce() -> Result<(), Error> + UnwindSafe,
281>(
282 user_mapper: &T,
283 uid: impl Into<Uid> + UnwindSafe,
284 gid: impl Into<Gid> + UnwindSafe,
285 func: Fn,
286) -> Result<(), Error> {
287 let pipe = new_pipe()?;
288 let child_pipe = new_pipe()?;
289 let mut clone_args = CloneArgs::default();
290 clone_args.flag_newuser();
291 match unsafe { clone3(&clone_args) }? {
292 CloneResult::Child => {
293 let _ = catch_unwind(move || {
294 let rx = pipe.rx();
295 let tx = child_pipe.tx();
296 exit_child(move || -> Result<(), Error> {
297 read_ok(rx)?;
298 write_result(
299 tx,
300 user_mapper
301 .set_user(uid.into(), gid.into())
302 .and_then(|_| func()),
303 )?
304 }())
305 });
306 unsafe { nix::libc::_exit(2) }
307 }
308 CloneResult::Parent { child } => {
309 let child = unsafe { OwnedPid::from_raw(child) };
310 let rx = child_pipe.rx();
311 let tx = pipe.tx();
312 user_mapper.run_map_user(child.as_raw())?;
313 write_ok(tx)?;
315 read_result(rx)??;
317 child.wait_success()
318 }
319 }
320}
321
322pub fn run_as_root<
323 T: UserMapper + RefUnwindSafe + ?Sized,
324 Fn: FnOnce() -> Result<(), Error> + UnwindSafe,
325>(
326 user_mapper: &T,
327 func: Fn,
328) -> Result<(), Error> {
329 run_as_user(user_mapper, 0, 0, func)
330}
331
332fn is_id_mapped<T>(id_map: &[IdMap<T>], id: T) -> bool
333where
334 T: Copy + Into<uid_t>,
335{
336 for v in id_map {
337 if v.container_id.into() + v.size <= id.into() {
338 continue;
339 }
340 if v.container_id.into() <= id.into() {
341 return true;
342 }
343 }
344 false
345}