running_process/
environment.rs1#[cfg(windows)]
11use std::ffi::c_void;
12use std::ffi::OsString;
13use std::io;
14
15pub fn user_baseline_environment() -> io::Result<Vec<(OsString, OsString)>> {
25 #[cfg(windows)]
26 {
27 let block = user_baseline_environment_block()?;
28 Ok(parse_windows_environment_block(&block))
29 }
30 #[cfg(unix)]
31 {
32 Ok(unix_login_baseline_environment().unwrap_or_else(|| std::env::vars_os().collect()))
33 }
34 #[cfg(not(any(windows, unix)))]
35 {
36 Ok(std::env::vars_os().collect())
37 }
38}
39
40#[cfg(unix)]
43const UNIX_LOGIN_DEFAULT_PATH: &str = if cfg!(target_os = "macos") {
44 "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
45} else {
46 "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
47};
48
49#[cfg(unix)]
53fn unix_login_baseline_environment() -> Option<Vec<(OsString, OsString)>> {
54 use std::ffi::CStr;
55 use std::os::unix::ffi::OsStringExt;
56
57 let mut passwd: libc::passwd = unsafe { std::mem::zeroed() };
58 let mut result: *mut libc::passwd = std::ptr::null_mut();
59 let mut buf = vec![0u8; 1024];
63 loop {
64 let rc = unsafe {
65 libc::getpwuid_r(
66 libc::getuid(),
67 &mut passwd,
68 buf.as_mut_ptr().cast(),
69 buf.len(),
70 &mut result,
71 )
72 };
73 if rc == libc::ERANGE && buf.len() < 1 << 20 {
74 buf.resize(buf.len() * 2, 0);
75 continue;
76 }
77 if rc != 0 || result.is_null() {
78 return None;
79 }
80 break;
81 }
82
83 let field = |ptr: *const libc::c_char| -> Option<OsString> {
84 if ptr.is_null() {
85 return None;
86 }
87 let bytes = unsafe { CStr::from_ptr(ptr) }.to_bytes();
88 (!bytes.is_empty()).then(|| OsString::from_vec(bytes.to_vec()))
89 };
90 let name = field(passwd.pw_name)?;
91 let home = field(passwd.pw_dir)?;
92
93 let mut env: Vec<(OsString, OsString)> = vec![
94 (OsString::from("USER"), name.clone()),
95 (OsString::from("LOGNAME"), name),
96 (OsString::from("HOME"), home),
97 (
98 OsString::from("PATH"),
99 OsString::from(UNIX_LOGIN_DEFAULT_PATH),
100 ),
101 ];
102 if let Some(shell) = field(passwd.pw_shell) {
103 env.push((OsString::from("SHELL"), shell));
104 }
105 for (key, value) in std::env::vars_os() {
109 let carry = key == "LANG" || key == "TZ" || key == "TMPDIR" || {
110 key.to_str().is_some_and(|k| k.starts_with("LC_"))
111 };
112 if carry {
113 env.push((key, value));
114 }
115 }
116 Some(env)
117}
118
119#[cfg(windows)]
124pub fn user_baseline_environment_block() -> io::Result<Vec<u16>> {
125 use windows_sys::Win32::Foundation::CloseHandle;
126 use windows_sys::Win32::Security::{TOKEN_DUPLICATE, TOKEN_IMPERSONATE, TOKEN_QUERY};
127 use windows_sys::Win32::System::Environment::{
128 CreateEnvironmentBlock, DestroyEnvironmentBlock,
129 };
130 use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
131
132 let mut token = std::ptr::null_mut();
139 let opened = unsafe {
140 OpenProcessToken(
141 GetCurrentProcess(),
142 TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE,
143 &mut token,
144 )
145 };
146 if opened == 0 {
147 return Err(io::Error::last_os_error());
148 }
149
150 let mut raw_block: *mut c_void = std::ptr::null_mut();
151 let created = unsafe { CreateEnvironmentBlock(&mut raw_block, token, 0) };
152 let create_error = if created == 0 {
153 Some(io::Error::last_os_error())
154 } else {
155 None
156 };
157 unsafe {
158 CloseHandle(token);
159 }
160 if let Some(error) = create_error {
161 return Err(error);
162 }
163
164 let copied = unsafe { copy_windows_environment_block(raw_block.cast::<u16>()) };
165 unsafe {
166 DestroyEnvironmentBlock(raw_block);
167 }
168 Ok(copied)
169}
170
171#[cfg(windows)]
172unsafe fn copy_windows_environment_block(cursor: *const u16) -> Vec<u16> {
173 let mut len = 0usize;
174 loop {
175 if *cursor.add(len) == 0 && *cursor.add(len + 1) == 0 {
176 len += 2;
177 break;
178 }
179 len += 1;
180 }
181 std::slice::from_raw_parts(cursor, len).to_vec()
182}
183
184#[cfg(windows)]
185fn parse_windows_environment_block(block: &[u16]) -> Vec<(OsString, OsString)> {
186 use std::os::windows::ffi::OsStringExt;
187
188 let mut env = Vec::new();
189 let mut offset = 0usize;
190 while offset < block.len() && block[offset] != 0 {
191 let Some(relative_end) = block[offset..].iter().position(|value| *value == 0) else {
192 break;
193 };
194 let end = offset + relative_end;
195 let entry = &block[offset..end];
196 if let Some(separator) = entry
200 .iter()
201 .enumerate()
202 .skip(1)
203 .find_map(|(index, value)| (*value == b'=' as u16).then_some(index))
204 {
205 let key = OsString::from_wide(&entry[..separator]);
206 let value = OsString::from_wide(&entry[separator + 1..]);
207 env.push((key, value));
208 }
209 offset = end + 1;
210 }
211 env
212}
213
214#[cfg(all(test, unix))]
215mod unix_tests {
216 use super::*;
217
218 #[test]
219 fn login_baseline_contains_identity_and_default_path() {
220 let env = user_baseline_environment().unwrap();
221 let get = |name: &str| {
222 env.iter()
223 .find(|(key, _)| key == name)
224 .map(|(_, value)| value.clone())
225 };
226 let user = get("USER").expect("baseline must contain USER");
227 assert!(!user.is_empty());
228 assert_eq!(get("LOGNAME").as_ref(), Some(&user));
229 let home = get("HOME").expect("baseline must contain HOME");
230 assert!(!home.is_empty());
231 let path = get("PATH").expect("baseline must contain PATH");
232 assert!(!path.is_empty());
233 }
234
235 #[test]
236 fn login_baseline_does_not_leak_arbitrary_process_vars() {
237 std::env::set_var("RUNNING_PROCESS_BASELINE_CANARY", "1");
240 let env = unix_login_baseline_environment().expect("test user must have a passwd entry");
241 assert!(
242 !env.iter()
243 .any(|(key, _)| key == "RUNNING_PROCESS_BASELINE_CANARY"),
244 "process-local variables must not leak into the login baseline"
245 );
246 std::env::remove_var("RUNNING_PROCESS_BASELINE_CANARY");
247 }
248}
249
250#[cfg(all(test, windows))]
251mod tests {
252 use super::*;
253 use std::ffi::OsStr;
254 use std::os::windows::ffi::OsStrExt;
255
256 #[test]
257 fn parser_preserves_drive_current_directory_entries() {
258 let block: Vec<u16> = OsStr::new("=C:=C:\\work")
259 .encode_wide()
260 .chain(std::iter::once(0))
261 .chain(OsStr::new("Path=C:\\Windows").encode_wide())
262 .chain(std::iter::once(0))
263 .chain(std::iter::once(0))
264 .collect();
265 assert_eq!(
266 parse_windows_environment_block(&block),
267 vec![
268 (OsString::from("=C:"), OsString::from("C:\\work")),
269 (OsString::from("Path"), OsString::from("C:\\Windows")),
270 ]
271 );
272 }
273
274 #[test]
275 fn live_user_baseline_is_double_nul_terminated() {
276 let block = user_baseline_environment_block().unwrap();
277 assert!(block.len() >= 2);
278 assert_eq!(&block[block.len() - 2..], &[0, 0]);
279 }
280
281 #[test]
286 fn live_user_baseline_contains_username() {
287 let env = user_baseline_environment().unwrap();
288 let username = env
289 .iter()
290 .find(|(key, _)| key.eq_ignore_ascii_case("USERNAME"))
291 .map(|(_, value)| value.clone());
292 let username = username.expect("baseline environment must contain USERNAME");
293 assert!(!username.is_empty(), "USERNAME must be non-empty");
294 if let Ok(live) = std::env::var("USERNAME") {
295 assert_eq!(
296 username.to_string_lossy(),
297 live,
298 "baseline USERNAME must match the live login USERNAME"
299 );
300 }
301 }
302}