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}