secure_execution/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#![no_std]

//! This crate provides a cross-platform function to determine if the running executable
//! requires secure execution.
//!
//! See the documentation of [`requires_secure_execution`] for details.

use {
    cfg_if::cfg_if,
    core::sync::atomic::{AtomicUsize, Ordering::Relaxed},
};

/// Returns whether the running executable requires secure execution.
///
/// This property is relevant for code that might be executed as part of a set-user-ID or
/// set-group-ID binary or similar.
///
/// Quoting the glibc manual pages:
///
/// > The  GNU-specific `secure_getenv()` function is just like `getenv()` except that it
/// > returns `NULL` in cases where "secure execution" is required.
/// >
/// > The `secure_getenv()` function is intended for use in general-purpose libraries to
/// > avoid vulnerabilities that could occur if set-user-ID or set-group-ID programs
/// > accidentally trusted the environment.
///
/// Quoting the OpenBSD manual pages:
///
/// > In particular, it is wise to use \[this property] to determine if a pathname
/// > returned from a `getenv()` call may safely be used to `open()` the specified file.
///
/// How this function determines this property depends on the `target_os` value.
///
/// - If `target_os` is one of `linux` or `android`, the `AT_SECURE` value from
///   `getauxval` is used. See [`getauxval(3)`] for details.
///
///   [`getauxval(3)`]: https://man7.org/linux/man-pages/man3/getauxval.3.html
///
/// - Otherwise, if `target_os` is one of `macos`, `ios`, `watchos`, `tvos`, `visionos`,
///   `dragonfly`, `freebsd`, `illumos`, `netbsd`, `openbsd`, or `solaris`, the return
///   value of `issetugid` is used.
///
///   The behavior of this function differs between operating systems, but it is always
///   defined to be used for this purpose. See for example the manual pages of [OpenBSD]
///   and [FreeBSD].
///
///   Note that, on FreeBSD and other operating systems using the same model, the return
///   value of `issetugid` can change at runtime. But this function always caches the
///   result when it is called for the first time.
///
///   [OpenBSD]: https://man.openbsd.org/issetugid.2
///   [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=issetugid
///
/// - Otherwise, if `cfg(unix)`, this function always returns `true`. As of this
///   writing, this affects the following `target_os` values:
///
///   `aix`, `emscripten`, `espidf`, `fuchsia`, `haiku`, `horizon`, `hurd`, `l4re`, `nto`,
///   `nuttx`, `redox`, `rtems`, `vita`, and `vxworks`
///
/// - Otherwise, this function always returns `false`. As of this writing, this affects
///   the following `target_os` values:
///
///   `cuda`, `hermit`, `psp`, `solid_asp3`, `teeos`, `trusty`, `uefi`, `wasi`, `windows`,
///   `xous`, and `zkvm`
#[inline(always)]
pub fn requires_secure_execution() -> bool {
    const FALSE: usize = 0;
    const TRUE: usize = 1;
    const TODO: usize = 2;
    static STATE: AtomicUsize = AtomicUsize::new(TODO);

    match STATE.load(Relaxed) {
        FALSE => false,
        TRUE => true,
        _ => {
            let state = requires_secure_execution_uncached();
            STATE.store(state as usize, Relaxed);
            state
        }
    }
}

fn requires_secure_execution_uncached() -> bool {
    cfg_if! {
        if #[cfg(any(
            target_os = "linux",
            target_os = "android",
        ))] {
            // https://man7.org/linux/man-pages/man3/getauxval.3.html
            //     AT_SECURE
            //            Has a nonzero value if this executable should be treated
            //            securely.  Most commonly, a nonzero value indicates that
            //            the process is executing a set-user-ID or set-group-ID
            //            binary (so that its real and effective UIDs or GIDs differ
            //            from one another), or that it gained capabilities by
            //            executing a binary file that has capabilities (see
            //            capabilities(7)).  Alternatively, a nonzero value may be
            //            triggered by a Linux Security Module.  When this value is
            //            nonzero, the dynamic linker disables the use of certain
            //            environment variables (see ld-linux.so(8)) and glibc
            //            changes other aspects of its behavior.  (See also
            //            secure_getenv(3).)
            use core::ffi::c_ulong;
            #[link(name = "c")]
            unsafe extern {
                safe fn getauxval(ty: c_ulong) -> c_ulong;
            }
            const AT_SECURE: c_ulong = 23;
            getauxval(AT_SECURE) != 0
        } else if #[cfg(any(
            target_os = "macos",
            target_os = "ios",
            target_os = "watchos",
            target_os = "tvos",
            target_os = "visionos",
            target_os = "dragonfly",
            target_os = "freebsd",
            target_os = "illumos",
            target_os = "netbsd",
            target_os = "openbsd",
            target_os = "solaris",
        ))] {
            // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/issetugid.2.html
            //     The issetugid() system call returns 1 if the process environment or memory
            //     address space is considered ``tainted'', and returns 0 otherwise.
            //
            //     A process is tainted if it was created as a result of an execve(2) system
            //     call which had either of the setuid or setgid bits set (and extra privileges
            //     were given as a result) or if it has changed any of its real,
            //     effective or saved user or group ID's since it began execution.
            //
            //     This system call exists so that library routines (eg: libc, libtermcap)
            //     can reliably determine if it is safe to use information that was obtained
            //     from the user, in particular the results from getenv(3) should be viewed
            //     with suspicion if it is used to control operation.
            //
            // The behavior on OpenBSD and some other BSDs differs since it is not affected by id
            // changes at runtime. Either way, both families define that this function should be
            // used to determine the secure-execution status.
            //
            // https://man.openbsd.org/issetugid.2
            //     The issetugid() function returns 1 if the process was made setuid or setgid as
            //     the result of the last or other previous execve() system calls. Otherwise it
            //     returns 0.
            //
            //     This system call exists so that library routines (inside libtermlib, libc, or
            //     other libraries) can guarantee safe behavior when used inside setuid or setgid
            //     programs. Some library routines may be passed insufficient information and
            //     hence not know whether the current program was started setuid or setgid because
            //     higher level calling code may have made changes to the uid, euid, gid, or egid.
            //     Hence these low-level library routines are unable to determine if they are
            //     being run with elevated or normal privileges.
            //
            //     In particular, it is wise to use this call to determine if a pathname returned
            //     from a getenv() call may safely be used to open() the specified file. Quite
            //     often this is not wise because the status of the effective uid is not known.
            //
            //     The issetugid() system call's result is unaffected by calls to setuid(),
            //     setgid(), or other such calls. In case of a fork(), the child process inherits
            //     the same status.
            //
            //     The status of issetugid() is only affected by execve(). If a child process
            //     executes a new executable file, a new issetugid status will be determined. This
            //     status is based on the existing process's uid, euid, gid, and egid permissions
            //     and on the modes of the executable file. If the new executable file modes are
            //     setuid or setgid, or if the existing process is executing the new image with
            //     uid != euid or gid != egid, the new process will be considered issetugid.
            use core::ffi::c_int;
            #[link(name = "c")]
            unsafe extern {
                safe fn issetugid() -> c_int;
            }
            issetugid() != 0
        } else if #[cfg(unix)] {
            true
        } else {
            false
        }
    }
}