user/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use common::singleton::Singleton;
4use nix::{
5    errno::Errno,
6    unistd::{ResGid, ResUid, getresgid, getresuid, setresgid, setresuid},
7};
8use std::{error, fmt, sync::LazyLock};
9
10/// The Real, Effective, and Saved UID of the application.
11pub static USER: LazyLock<ResUid> = LazyLock::new(|| getresuid().expect("Failed to get UID!"));
12
13/// The Real, Effective, and Saved GID of the application.
14pub static GROUP: LazyLock<ResGid> = LazyLock::new(|| getresgid().expect("Failed to get GID!"));
15
16/// Whether the system is actually running under SetUid. If false, all functions here
17/// are no-ops.
18pub static SETUID: LazyLock<bool> = LazyLock::new(|| USER.effective != USER.real);
19
20/// An error when trying to change UID/GID.
21#[derive(Debug)]
22pub struct Error {
23    /// The UID we were trying to change to
24    uid: ResUid,
25
26    /// The error we got from the syscall.
27    errno: Errno,
28}
29impl fmt::Display for Error {
30    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
31        let current = getresgid();
32        if let Ok(uid) = current {
33            write!(
34                f,
35                "Failed to change UID from: ({}, {}, {}) to ({}, {}, {})",
36                uid.real,
37                uid.effective,
38                uid.saved,
39                self.uid.real,
40                self.uid.effective,
41                self.uid.saved
42            )
43        } else {
44            write!(
45                f,
46                "Failed to change UID to ({}, {}, {})",
47                self.uid.real, self.uid.effective, self.uid.saved
48            )
49        }
50    }
51}
52impl error::Error for Error {
53    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
54        Some(&self.errno as &dyn error::Error)
55    }
56}
57
58/// A SetUID mode.
59#[derive(Debug, Copy, Clone)]
60pub enum Mode {
61    /// Transition to the Real user, setting both Real and Effective
62    /// to `USER.real`, while saving Effective to Saved.
63    Real,
64
65    /// Transition to the Effective user, setting both Real and Effective
66    /// to `USER.effective`, while saving Real to Saved.
67    Effective,
68
69    /// The current operating mode. This is functionally a no-op except for
70    /// in drop, where it drops whatever the current mode happens to be.
71    Existing,
72
73    /// Revert to the program's original operating mode. For set, this
74    /// mode is functionally identical to using revert(). For drop, it
75    /// acts as user::revert().
76    Original,
77}
78
79/// Set the Mode.
80/// This function can never be misused to lock out the process
81/// from returning to the original, or any other mode.
82/// This function returns an error if the mode could not be
83/// changed. Otherwise, it returns the old mode for use in
84/// `restore()`.
85///
86/// ## Examples
87/// ```rust
88/// user::set(user::Mode::Real).unwrap();
89/// ```
90///
91/// ## Notes
92///
93/// * user::set(Mode::Original) is functionally identical to user::revert()
94/// * user::set(Mode::Existing) is a no-op.
95///
96/// ## Thread Safety
97///
98/// This function is not thread safe. Multiple threads can change the state
99/// using this function. If you need to ensure that everything executed between
100/// a `set()` and `restore()` block is run under the desired user, use `run_as` or
101/// its mode-specific variants.
102pub fn set(mode: Mode) -> Result<(ResUid, ResGid), Errno> {
103    if !*SETUID {
104        return Ok((*USER, *GROUP));
105    }
106
107    let uid = getresuid()?;
108    let gid = getresgid()?;
109
110    match mode {
111        Mode::Real => {
112            setresuid(USER.real, USER.real, USER.effective)?;
113            setresgid(GROUP.real, GROUP.real, GROUP.effective)?;
114        }
115        Mode::Effective => {
116            setresuid(USER.effective, USER.effective, USER.real)?;
117            setresgid(GROUP.effective, GROUP.effective, GROUP.real)?;
118        }
119        Mode::Original => revert()?,
120        Mode::Existing => {}
121    }
122
123    Ok((uid, gid))
124}
125
126/// Get the current user mode
127/// Note that this is not thread-safe, and your program can suffer
128/// from TOC-TOU problems if you assume this value will remain the same
129/// when you actually need to perform a privileged operation in a multi-threaded
130/// environment.
131pub fn current() -> Result<Mode, Errno> {
132    let uid = getresuid()?.real;
133    if uid == USER.real {
134        Ok(Mode::Real)
135    } else if uid == USER.effective {
136        Ok(Mode::Effective)
137    } else {
138        Err(Errno::EINVAL)
139    }
140}
141
142/// Revert the Mode to the original.
143/// This function returns to the values of `USER` and `GROUP`.
144/// This function can fail if the underlying syscall does.
145pub fn revert() -> Result<(), Errno> {
146    setresuid(USER.real, USER.effective, USER.saved)?;
147    setresgid(GROUP.real, GROUP.effective, GROUP.saved)
148}
149
150/// Destructively change mode, preventing the process from returning.
151/// This function will set Real, Effective, and Saved values to the
152/// desired Mode. This prevents the process from changing their mode
153/// ## Examples
154/// ```rust
155/// user::drop(user::Mode::Real).unwrap();
156/// // This will only fail if we were SetUID.
157/// if user::USER.real != user::USER.effective {
158///     user::set(user::Mode::Effective).expect_err("Cannot return!");
159/// }
160/// ```
161pub fn drop(mode: Mode) -> Result<(), Errno> {
162    match mode {
163        Mode::Real => {
164            setresuid(USER.real, USER.real, USER.real)?;
165            setresgid(GROUP.real, GROUP.real, GROUP.real)
166        }
167        Mode::Effective => {
168            setresuid(USER.effective, USER.effective, USER.effective)?;
169            setresgid(GROUP.effective, GROUP.effective, GROUP.effective)
170        }
171        Mode::Original => revert(),
172        Mode::Existing => {
173            let (user, group) = (getresuid()?, getresgid()?);
174            setresuid(user.real, user.real, user.real)?;
175            setresgid(group.real, group.real, group.real)
176        }
177    }
178}
179
180/// Restore a saved Uid/Gid combination
181/// This function fails if the syscall does.
182/// ## Examples
183/// ```rust
184/// let saved = user::set(user::Mode::Real).unwrap();
185/// // Do work.
186/// user::restore(saved).unwrap()
187/// ```
188pub fn restore((uid, gid): (ResUid, ResGid)) -> Result<(), Errno> {
189    if !*SETUID {
190        return Ok(());
191    }
192
193    setresuid(uid.real, uid.effective, uid.saved)?;
194    setresgid(gid.real, gid.effective, gid.saved)
195}
196
197pub fn obtain_lock() -> Option<Singleton> {
198    if *crate::SETUID {
199        Singleton::new()
200    } else {
201        None
202    }
203}
204
205/// This is a thread-safe wrapper that sets the mode, runs the closure/expression,
206/// then returns to the mode before the call. You can use this in multi-threaded
207/// environments, and it is guaranteed the content of the closure/expression will
208/// be run under the requested Mode.
209#[macro_export]
210macro_rules! run_as {
211    ($mode:path, $ret:ty, $body:block) => {{
212        {
213            let lock = user::obtain_lock();
214            match user::set($mode) {
215                Ok(__saved) => {
216                    let __result = (|| -> $ret { $body })();
217                    user::restore(__saved).map(|e| __result)
218                }
219                Err(e) => Err(e),
220            }
221        }
222    }};
223
224    ($mode:path, $body:block) => {{
225        {
226            let lock = user::obtain_lock();
227            match user::set($mode) {
228                Ok(__saved) => {
229                    let __result = (|| $body)();
230                    user::restore(__saved).map(|e| __result)
231                }
232                Err(e) => Err(e),
233            }
234        }
235    }};
236
237    ($mode:path, $expr:expr) => {{
238        {
239            let lock = user::obtain_lock();
240            match user::set($mode) {
241                Ok(__saved) => {
242                    let __result = $expr;
243                    user::restore(__saved).map(|e| __result)
244                }
245                Err(e) => Err(e),
246            }
247        }
248    }};
249}
250
251/// Run the block/expression as the Real User. This is thread safe.
252#[macro_export]
253macro_rules! as_real {
254    ($ret:ty, $body:block) => {{ user::run_as!(user::Mode::Real, $ret, $body) }};
255    ($body:block) => {{ user::run_as!(user::Mode::Real, $body) }};
256    ($expr:expr) => {{ user::run_as!(user::Mode::Real, { $expr }) }};
257}
258
259/// Run the block/expression as the Effective User. This is thread safe.
260#[macro_export]
261macro_rules! as_effective {
262    ($ret:ty, $body:block) => {{ user::run_as!(user::Mode::Effective, $ret, $body) }};
263    ($body:block) => {{ user::run_as!(user::Mode::Effective, $body) }};
264    ($expr:expr) => {{ user::run_as!(user::Mode::Effective, { $expr }) }};
265}