running_process/broker/lifecycle/
privilege.rs1pub const ALLOW_PRIVILEGED_ENV: &str = "RUNNING_PROCESS_BROKER_ALLOW_PRIVILEGED";
13
14#[derive(Debug, thiserror::Error)]
16pub enum PrivilegeError {
17 #[error(
19 "running-process-broker-v1 refuses to run as {identity} by default; set {ALLOW_PRIVILEGED_ENV}=1 only for isolated test environments"
20 )]
21 Privileged {
22 identity: PrivilegedIdentity,
24 },
25 #[error("failed to determine broker process privilege: {0}")]
27 PlatformLookup(String),
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum PrivilegedIdentity {
33 UnixRoot,
35 WindowsLocalSystem,
37}
38
39impl std::fmt::Display for PrivilegedIdentity {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 match self {
42 Self::UnixRoot => f.write_str("root (effective uid 0)"),
43 Self::WindowsLocalSystem => f.write_str("Windows LocalSystem (S-1-5-18)"),
44 }
45 }
46}
47
48pub fn refuse_privileged_run() -> Result<(), PrivilegeError> {
54 if allow_privileged_from_env() {
55 return Ok(());
56 }
57 refuse_process_privilege(current_process_privilege()?)
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61struct ProcessPrivilege {
62 identity: Option<PrivilegedIdentity>,
63}
64
65impl ProcessPrivilege {
66 const fn unprivileged() -> Self {
67 Self { identity: None }
68 }
69
70 const fn privileged(identity: PrivilegedIdentity) -> Self {
71 Self {
72 identity: Some(identity),
73 }
74 }
75}
76
77fn refuse_process_privilege(privilege: ProcessPrivilege) -> Result<(), PrivilegeError> {
78 match privilege.identity {
79 Some(identity) => Err(PrivilegeError::Privileged { identity }),
80 None => Ok(()),
81 }
82}
83
84fn allow_privileged_from_env() -> bool {
85 let value = std::env::var(ALLOW_PRIVILEGED_ENV).ok();
86 allow_privileged_env_value(value.as_deref())
87}
88
89fn allow_privileged_env_value(value: Option<&str>) -> bool {
90 value == Some("1")
91}
92
93fn current_process_privilege() -> Result<ProcessPrivilege, PrivilegeError> {
94 platform_current_process_privilege()
95}
96
97#[cfg(unix)]
98fn platform_current_process_privilege() -> Result<ProcessPrivilege, PrivilegeError> {
99 let euid = unsafe { libc::geteuid() };
100 Ok(privilege_from_unix_euid(euid))
101}
102
103#[cfg(unix)]
104fn privilege_from_unix_euid(euid: libc::uid_t) -> ProcessPrivilege {
105 if euid == 0 {
106 ProcessPrivilege::privileged(PrivilegedIdentity::UnixRoot)
107 } else {
108 ProcessPrivilege::unprivileged()
109 }
110}
111
112#[cfg(windows)]
113fn platform_current_process_privilege() -> Result<ProcessPrivilege, PrivilegeError> {
114 let sid = windows_current_user_sid_bytes()?;
115 if is_windows_local_system_sid(&sid) {
116 Ok(ProcessPrivilege::privileged(
117 PrivilegedIdentity::WindowsLocalSystem,
118 ))
119 } else {
120 Ok(ProcessPrivilege::unprivileged())
121 }
122}
123
124#[cfg(all(not(unix), not(windows)))]
125fn platform_current_process_privilege() -> Result<ProcessPrivilege, PrivilegeError> {
126 Ok(ProcessPrivilege::unprivileged())
127}
128
129#[cfg(windows)]
130fn windows_current_user_sid_bytes() -> Result<Vec<u8>, PrivilegeError> {
131 use std::ptr;
132 use winapi::shared::winerror::ERROR_INSUFFICIENT_BUFFER;
133 use winapi::um::errhandlingapi::GetLastError;
134 use winapi::um::processthreadsapi::{GetCurrentProcess, OpenProcessToken};
135 use winapi::um::securitybaseapi::{GetLengthSid, GetTokenInformation, IsValidSid};
136 use winapi::um::winnt::{TokenUser, HANDLE, TOKEN_QUERY, TOKEN_USER};
137
138 unsafe {
143 let mut token: HANDLE = ptr::null_mut();
144 if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) == 0 {
145 return Err(PrivilegeError::PlatformLookup(format!(
146 "OpenProcessToken failed (GetLastError={})",
147 GetLastError()
148 )));
149 }
150 let token = TokenHandle(token);
151
152 let mut required_size = 0_u32;
153 let ok = GetTokenInformation(token.0, TokenUser, ptr::null_mut(), 0, &mut required_size);
154 let last = GetLastError();
155 if ok != 0 || last != ERROR_INSUFFICIENT_BUFFER {
156 return Err(PrivilegeError::PlatformLookup(format!(
157 "GetTokenInformation size query failed (ok={ok}, GetLastError={last})"
158 )));
159 }
160 if required_size == 0 {
161 return Err(PrivilegeError::PlatformLookup(
162 "GetTokenInformation reported 0 required bytes".into(),
163 ));
164 }
165
166 let mut buf = vec![0_u8; required_size as usize];
167 if GetTokenInformation(
168 token.0,
169 TokenUser,
170 buf.as_mut_ptr().cast(),
171 required_size,
172 &mut required_size,
173 ) == 0
174 {
175 return Err(PrivilegeError::PlatformLookup(format!(
176 "GetTokenInformation real query failed (GetLastError={})",
177 GetLastError()
178 )));
179 }
180
181 let token_user: *const TOKEN_USER = buf.as_ptr().cast();
182 let sid = (*token_user).User.Sid;
183 if sid.is_null() {
184 return Err(PrivilegeError::PlatformLookup(
185 "TOKEN_USER returned a null SID pointer".into(),
186 ));
187 }
188 if IsValidSid(sid) == 0 {
189 return Err(PrivilegeError::PlatformLookup(
190 "IsValidSid returned false".into(),
191 ));
192 }
193
194 let len = GetLengthSid(sid) as usize;
195 if len == 0 || len > 1024 {
196 return Err(PrivilegeError::PlatformLookup(format!(
197 "GetLengthSid returned implausible length {len}"
198 )));
199 }
200 Ok(std::slice::from_raw_parts(sid as *const u8, len).to_vec())
201 }
202}
203
204#[cfg(windows)]
205struct TokenHandle(winapi::um::winnt::HANDLE);
206
207#[cfg(windows)]
208impl Drop for TokenHandle {
209 fn drop(&mut self) {
210 unsafe {
211 winapi::um::handleapi::CloseHandle(self.0);
212 }
213 }
214}
215
216#[cfg(windows)]
217fn is_windows_local_system_sid(sid: &[u8]) -> bool {
218 const LOCAL_SYSTEM_SID: &[u8] = &[1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0];
219 sid == LOCAL_SYSTEM_SID
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn refuses_privileged_identity() {
228 let err =
229 refuse_process_privilege(ProcessPrivilege::privileged(PrivilegedIdentity::UnixRoot))
230 .unwrap_err();
231 assert!(matches!(
232 err,
233 PrivilegeError::Privileged {
234 identity: PrivilegedIdentity::UnixRoot
235 }
236 ));
237 }
238
239 #[test]
240 fn allows_unprivileged_identity() {
241 refuse_process_privilege(ProcessPrivilege::unprivileged()).unwrap();
242 }
243
244 #[test]
245 fn allow_env_value_requires_exact_one() {
246 assert!(allow_privileged_env_value(Some("1")));
247 assert!(!allow_privileged_env_value(None));
248 assert!(!allow_privileged_env_value(Some("")));
249 assert!(!allow_privileged_env_value(Some("true")));
250 assert!(!allow_privileged_env_value(Some("yes")));
251 }
252
253 #[cfg(unix)]
254 #[test]
255 fn unix_root_detection_uses_effective_uid_zero() {
256 assert_eq!(
257 privilege_from_unix_euid(0),
258 ProcessPrivilege::privileged(PrivilegedIdentity::UnixRoot)
259 );
260 assert_eq!(
261 privilege_from_unix_euid(1000),
262 ProcessPrivilege::unprivileged()
263 );
264 }
265
266 #[cfg(windows)]
267 #[test]
268 fn windows_local_system_sid_is_detected() {
269 assert!(is_windows_local_system_sid(&[
270 1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0
271 ]));
272 assert!(!is_windows_local_system_sid(&[
273 1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0
274 ]));
275 }
276}