sbox/
user.rs

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/// Represents mapping for IDs from host namespace to container namespace.
21#[derive(Clone, Debug)]
22pub struct IdMap<T> {
23    /// First ID in container namespace.
24    pub container_id: T,
25    /// First ID in host namespace.
26    pub host_id: T,
27    /// Amount of mapped IDs.
28    pub size: u32,
29}
30
31impl<T: From<uid_t>> IdMap<T> {
32    /// Maps specified host ID as root (ID = 0) in container namespace.
33    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
42/// Represents mapper for user IDs and group IDs in container namespace.
43pub trait UserMapper: Send + Sync + Debug + RefUnwindSafe {
44    /// Runs mapping for new user namespace initialized by specified process.
45    fn run_map_user(&self, pid: Pid) -> Result<(), Error>;
46
47    /// Sets user ID and group ID for current process in user namespace.
48    fn set_user(&self, uid: Uid, gid: Gid) -> Result<(), Error>;
49
50    /// Verifies that specified user ID is represented in container.
51    fn is_uid_mapped(&self, id: Uid) -> bool;
52
53    /// Verifies that specified group ID is represented in container.
54    fn is_gid_mapped(&self, id: Gid) -> bool;
55
56    /// Calculates amount of mapped user IDs.
57    fn uid_count(&self) -> u32;
58
59    /// Calculates amount of mapped group IDs.
60    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    /// Maps uid and gid as container root.
72    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
81/// Creates user mapper for current process uid and gid.
82impl Default for ProcUserMapper {
83    fn default() -> Self {
84        Self::new_root(getuid(), getgid())
85    }
86}
87
88impl UserMapper for ProcUserMapper {
89    /// Runs mapping for new user namespace initialized by specified process.
90    fn run_map_user(&self, _pid: Pid) -> Result<(), Error> {
91        todo!()
92    }
93
94    /// Sets user ID and group ID for current process in user namespace.
95    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    /// Verifies that specified user ID is represented in container.
108    fn is_uid_mapped(&self, uid: Uid) -> bool {
109        is_id_mapped(&self.uid_map, uid)
110    }
111
112    /// Verifies that specified group ID is represented in container.
113    fn is_gid_mapped(&self, gid: Gid) -> bool {
114        is_id_mapped(&self.gid_map, gid)
115    }
116
117    /// Calculates amount of mapped user IDs.
118    fn uid_count(&self) -> u32 {
119        self.uid_map.iter().fold(0, |acc, x| acc + x.size)
120    }
121
122    /// Calculates amount of mapped group IDs.
123    fn gid_count(&self) -> u32 {
124        self.gid_map.iter().fold(0, |acc, x| acc + x.size)
125    }
126}
127
128/// Represents user mapper implemented using new{u,g}idmap.
129///
130/// Uses new{u,g}idmap binaries from following paths:
131///   * `/bin/newuidmap`
132///   * `/bin/newgidmap`
133#[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    /// Maps uid and gid as container root.
143    ///
144    /// Uses new{u,g}idmap binaries from following paths:
145    ///   * `/bin/newuidmap`
146    ///   * `/bin/newgidmap`
147    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    /// Maps uid and gid as container root, subuid and subgid as other users.
157    ///
158    /// Uses new{u,g}idmap binaries from following paths:
159    ///   * `/bin/newuidmap`
160    ///   * `/bin/newgidmap`
161    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
229/// Creates user mapper for current process uid and gid.
230impl Default for BinNewIdMapper {
231    fn default() -> Self {
232        Self::new_root(getuid(), getgid())
233    }
234}
235
236impl UserMapper for BinNewIdMapper {
237    /// Runs mapping for new user namespace initialized by specified process.
238    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    /// Sets user ID and group ID for current process in user namespace.
247    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    /// Verifies that specified user ID is represented in container.
258    fn is_uid_mapped(&self, uid: Uid) -> bool {
259        is_id_mapped(&self.uid_map, uid)
260    }
261
262    /// Verifies that specified group ID is represented in container.
263    fn is_gid_mapped(&self, gid: Gid) -> bool {
264        is_id_mapped(&self.gid_map, gid)
265    }
266
267    /// Calculates amount of mapped user IDs.
268    fn uid_count(&self) -> u32 {
269        self.uid_map.iter().fold(0, |acc, x| acc + x.size)
270    }
271
272    /// Calculates amount of mapped group IDs.
273    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            // Unlock child process.
314            write_ok(tx)?;
315            // Await child process result.
316            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}