secure_execution/
lib.rs

1#![no_std]
2
3//! This crate provides a cross-platform function to determine if the running executable
4//! requires secure execution.
5//!
6//! See the documentation of [`requires_secure_execution`] for details.
7
8use {
9    cfg_if::cfg_if,
10    core::sync::atomic::{AtomicUsize, Ordering::Relaxed},
11};
12
13/// Returns whether the running executable requires secure execution.
14///
15/// This property is relevant for code that might be executed as part of a set-user-ID or
16/// set-group-ID binary or similar.
17///
18/// Quoting the glibc manual pages:
19///
20/// > The  GNU-specific `secure_getenv()` function is just like `getenv()` except that it
21/// > returns `NULL` in cases where "secure execution" is required.
22/// >
23/// > The `secure_getenv()` function is intended for use in general-purpose libraries to
24/// > avoid vulnerabilities that could occur if set-user-ID or set-group-ID programs
25/// > accidentally trusted the environment.
26///
27/// Quoting the OpenBSD manual pages:
28///
29/// > In particular, it is wise to use \[this property] to determine if a pathname
30/// > returned from a `getenv()` call may safely be used to `open()` the specified file.
31///
32/// How this function determines this property depends on the `target_os` value.
33///
34/// - If `target_os` is one of `linux` or `android`, the `AT_SECURE` value from
35///   `getauxval` is used. See [`getauxval(3)`] for details.
36///
37///   [`getauxval(3)`]: https://man7.org/linux/man-pages/man3/getauxval.3.html
38///
39/// - Otherwise, if `target_os` is one of `macos`, `ios`, `watchos`, `tvos`, `visionos`,
40///   `dragonfly`, `freebsd`, `illumos`, `netbsd`, `openbsd`, or `solaris`, the return
41///   value of `issetugid` is used.
42///
43///   The behavior of this function differs between operating systems, but it is always
44///   defined to be used for this purpose. See for example the manual pages of [OpenBSD]
45///   and [FreeBSD].
46///
47///   Note that, on FreeBSD and other operating systems using the same model, the return
48///   value of `issetugid` can change at runtime. But this function always caches the
49///   result when it is called for the first time.
50///
51///   [OpenBSD]: https://man.openbsd.org/issetugid.2
52///   [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=issetugid
53///
54/// - Otherwise, if `cfg(unix)`, this function always returns `true`. As of this
55///   writing, this affects the following `target_os` values:
56///
57///   `aix`, `emscripten`, `espidf`, `fuchsia`, `haiku`, `horizon`, `hurd`, `l4re`, `nto`,
58///   `nuttx`, `redox`, `rtems`, `vita`, and `vxworks`
59///
60/// - Otherwise, this function always returns `false`. As of this writing, this affects
61///   the following `target_os` values:
62///
63///   `cuda`, `hermit`, `psp`, `solid_asp3`, `teeos`, `trusty`, `uefi`, `wasi`, `windows`,
64///   `xous`, and `zkvm`
65#[inline(always)]
66pub fn requires_secure_execution() -> bool {
67    const FALSE: usize = 0;
68    const TRUE: usize = 1;
69    const TODO: usize = 2;
70    static STATE: AtomicUsize = AtomicUsize::new(TODO);
71
72    match STATE.load(Relaxed) {
73        FALSE => false,
74        TRUE => true,
75        _ => {
76            let state = requires_secure_execution_uncached();
77            STATE.store(state as usize, Relaxed);
78            state
79        }
80    }
81}
82
83fn requires_secure_execution_uncached() -> bool {
84    cfg_if! {
85        if #[cfg(any(
86            target_os = "linux",
87            target_os = "android",
88        ))] {
89            // https://man7.org/linux/man-pages/man3/getauxval.3.html
90            //     AT_SECURE
91            //            Has a nonzero value if this executable should be treated
92            //            securely.  Most commonly, a nonzero value indicates that
93            //            the process is executing a set-user-ID or set-group-ID
94            //            binary (so that its real and effective UIDs or GIDs differ
95            //            from one another), or that it gained capabilities by
96            //            executing a binary file that has capabilities (see
97            //            capabilities(7)).  Alternatively, a nonzero value may be
98            //            triggered by a Linux Security Module.  When this value is
99            //            nonzero, the dynamic linker disables the use of certain
100            //            environment variables (see ld-linux.so(8)) and glibc
101            //            changes other aspects of its behavior.  (See also
102            //            secure_getenv(3).)
103            use core::ffi::c_ulong;
104            #[link(name = "c")]
105            unsafe extern {
106                safe fn getauxval(ty: c_ulong) -> c_ulong;
107            }
108            const AT_SECURE: c_ulong = 23;
109            getauxval(AT_SECURE) != 0
110        } else if #[cfg(any(
111            target_os = "macos",
112            target_os = "ios",
113            target_os = "watchos",
114            target_os = "tvos",
115            target_os = "visionos",
116            target_os = "dragonfly",
117            target_os = "freebsd",
118            target_os = "illumos",
119            target_os = "netbsd",
120            target_os = "openbsd",
121            target_os = "solaris",
122        ))] {
123            // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/issetugid.2.html
124            //     The issetugid() system call returns 1 if the process environment or memory
125            //     address space is considered ``tainted'', and returns 0 otherwise.
126            //
127            //     A process is tainted if it was created as a result of an execve(2) system
128            //     call which had either of the setuid or setgid bits set (and extra privileges
129            //     were given as a result) or if it has changed any of its real,
130            //     effective or saved user or group ID's since it began execution.
131            //
132            //     This system call exists so that library routines (eg: libc, libtermcap)
133            //     can reliably determine if it is safe to use information that was obtained
134            //     from the user, in particular the results from getenv(3) should be viewed
135            //     with suspicion if it is used to control operation.
136            //
137            // The behavior on OpenBSD and some other BSDs differs since it is not affected by id
138            // changes at runtime. Either way, both families define that this function should be
139            // used to determine the secure-execution status.
140            //
141            // https://man.openbsd.org/issetugid.2
142            //     The issetugid() function returns 1 if the process was made setuid or setgid as
143            //     the result of the last or other previous execve() system calls. Otherwise it
144            //     returns 0.
145            //
146            //     This system call exists so that library routines (inside libtermlib, libc, or
147            //     other libraries) can guarantee safe behavior when used inside setuid or setgid
148            //     programs. Some library routines may be passed insufficient information and
149            //     hence not know whether the current program was started setuid or setgid because
150            //     higher level calling code may have made changes to the uid, euid, gid, or egid.
151            //     Hence these low-level library routines are unable to determine if they are
152            //     being run with elevated or normal privileges.
153            //
154            //     In particular, it is wise to use this call to determine if a pathname returned
155            //     from a getenv() call may safely be used to open() the specified file. Quite
156            //     often this is not wise because the status of the effective uid is not known.
157            //
158            //     The issetugid() system call's result is unaffected by calls to setuid(),
159            //     setgid(), or other such calls. In case of a fork(), the child process inherits
160            //     the same status.
161            //
162            //     The status of issetugid() is only affected by execve(). If a child process
163            //     executes a new executable file, a new issetugid status will be determined. This
164            //     status is based on the existing process's uid, euid, gid, and egid permissions
165            //     and on the modes of the executable file. If the new executable file modes are
166            //     setuid or setgid, or if the existing process is executing the new image with
167            //     uid != euid or gid != egid, the new process will be considered issetugid.
168            use core::ffi::c_int;
169            #[link(name = "c")]
170            unsafe extern {
171                safe fn issetugid() -> c_int;
172            }
173            issetugid() != 0
174        } else if #[cfg(unix)] {
175            true
176        } else {
177            false
178        }
179    }
180}