1use std::process::Command;
30
31pub struct SanitizedChild {
39 pid: u32,
40 #[cfg(windows)]
41 handle: windows::OwnedHandle,
42 #[cfg(unix)]
43 child: std::process::Child,
44}
45
46impl SanitizedChild {
47 pub fn id(&self) -> u32 {
49 self.pid
50 }
51
52 pub fn kill(&mut self) -> std::io::Result<()> {
55 #[cfg(windows)]
56 {
57 windows::terminate(&self.handle)
58 }
59 #[cfg(unix)]
60 {
61 self.child.kill()
62 }
63 }
64
65 pub fn wait(&mut self) -> std::io::Result<i32> {
68 #[cfg(windows)]
69 {
70 windows::wait(&self.handle)
71 }
72 #[cfg(unix)]
73 {
74 let status = self.child.wait()?;
75 Ok(exit_code(status))
76 }
77 }
78
79 pub fn try_wait(&mut self) -> std::io::Result<Option<i32>> {
82 #[cfg(windows)]
83 {
84 windows::try_wait(&self.handle)
85 }
86 #[cfg(unix)]
87 {
88 Ok(self.child.try_wait()?.map(exit_code))
89 }
90 }
91}
92
93#[cfg(unix)]
94fn exit_code(status: std::process::ExitStatus) -> i32 {
95 use std::os::unix::process::ExitStatusExt;
96 status
97 .code()
98 .unwrap_or_else(|| -status.signal().unwrap_or(1))
99}
100
101pub fn spawn(command: &mut Command) -> std::io::Result<SanitizedChild> {
104 #[cfg(windows)]
105 {
106 windows::spawn(command)
107 }
108 #[cfg(unix)]
109 {
110 unix::spawn(command)
111 }
112}
113
114#[cfg(windows)]
117mod windows {
118 use std::ffi::{OsStr, OsString};
119 use std::os::windows::ffi::OsStrExt;
120 use std::process::Command;
121
122 use winapi::shared::minwindef::{BOOL, DWORD, FALSE, TRUE};
123 use winapi::um::fileapi::{CreateFileW, OPEN_EXISTING};
124 use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE};
125 use winapi::um::minwinbase::SECURITY_ATTRIBUTES;
126 use winapi::um::processthreadsapi::{
127 CreateProcessW, DeleteProcThreadAttributeList, GetExitCodeProcess,
128 InitializeProcThreadAttributeList, TerminateProcess, UpdateProcThreadAttribute,
129 LPPROC_THREAD_ATTRIBUTE_LIST, PROCESS_INFORMATION,
130 };
131 use winapi::um::synchapi::WaitForSingleObject;
132 use winapi::um::winbase::{
133 CREATE_NEW_PROCESS_GROUP, CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT, DETACHED_PROCESS,
134 EXTENDED_STARTUPINFO_PRESENT, INFINITE, STARTF_USESTDHANDLES, STARTUPINFOEXW,
135 WAIT_OBJECT_0,
136 };
137 use winapi::um::winnt::{
138 FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE, HANDLE,
139 };
140
141 const PROC_THREAD_ATTRIBUTE_HANDLE_LIST: usize = 0x00020002;
153 const STILL_ACTIVE: u32 = 259;
154
155 pub struct OwnedHandle(HANDLE);
156
157 impl OwnedHandle {
158 pub fn as_raw(&self) -> HANDLE {
159 self.0
160 }
161 }
162
163 impl Drop for OwnedHandle {
164 fn drop(&mut self) {
165 if !self.0.is_null() && self.0 != INVALID_HANDLE_VALUE {
166 unsafe {
167 CloseHandle(self.0);
168 }
169 }
170 }
171 }
172
173 unsafe impl Send for OwnedHandle {}
176 unsafe impl Sync for OwnedHandle {}
177
178 fn open_nul(write: bool) -> std::io::Result<OwnedHandle> {
179 let path: Vec<u16> = OsStr::new("NUL")
180 .encode_wide()
181 .chain(std::iter::once(0))
182 .collect();
183 let mut sa: SECURITY_ATTRIBUTES = unsafe { std::mem::zeroed() };
184 sa.nLength = std::mem::size_of::<SECURITY_ATTRIBUTES>() as DWORD;
185 sa.bInheritHandle = TRUE as BOOL;
186 let access = if write { GENERIC_WRITE } else { GENERIC_READ };
187 let h = unsafe {
188 CreateFileW(
189 path.as_ptr(),
190 access,
191 FILE_SHARE_READ | FILE_SHARE_WRITE,
192 &mut sa as *mut SECURITY_ATTRIBUTES,
193 OPEN_EXISTING,
194 0,
195 std::ptr::null_mut(),
196 )
197 };
198 if h.is_null() || h == INVALID_HANDLE_VALUE {
199 return Err(std::io::Error::last_os_error());
200 }
201 Ok(OwnedHandle(h))
202 }
203
204 pub fn spawn(command: &mut Command) -> std::io::Result<super::SanitizedChild> {
205 let stdin = open_nul(false)?;
209 let stdout = open_nul(true)?;
210 let stderr = open_nul(true)?;
211
212 let mut cmdline = build_command_line(command.get_program(), command.get_args());
214
215 let envs: Vec<(OsString, Option<OsString>)> = command
216 .get_envs()
217 .map(|(k, v)| (k.to_os_string(), v.map(|v| v.to_os_string())))
218 .collect();
219 let env_block = if envs.is_empty() {
220 None
221 } else {
222 Some(build_env_block(envs))
223 };
224
225 let cwd_w: Option<Vec<u16>> = command.get_current_dir().map(|p| {
226 OsStr::new(p)
227 .encode_wide()
228 .chain(std::iter::once(0))
229 .collect()
230 });
231
232 let mut size: usize = 0;
234 unsafe {
235 InitializeProcThreadAttributeList(std::ptr::null_mut(), 1, 0, &mut size);
237 }
238 let mut attr_buf: Vec<u8> = vec![0; size];
240 let attr_list = attr_buf.as_mut_ptr() as LPPROC_THREAD_ATTRIBUTE_LIST;
241
242 let ok = unsafe { InitializeProcThreadAttributeList(attr_list, 1, 0, &mut size) };
243 if ok == FALSE {
244 return Err(std::io::Error::last_os_error());
245 }
246
247 let handle_list: [HANDLE; 3] = [stdin.as_raw(), stdout.as_raw(), stderr.as_raw()];
248 let ok = unsafe {
249 UpdateProcThreadAttribute(
250 attr_list,
251 0,
252 PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
253 handle_list.as_ptr() as *mut _,
254 std::mem::size_of::<[HANDLE; 3]>(),
255 std::ptr::null_mut(),
256 std::ptr::null_mut(),
257 )
258 };
259 if ok == FALSE {
260 let err = std::io::Error::last_os_error();
261 unsafe {
262 DeleteProcThreadAttributeList(attr_list);
263 }
264 return Err(err);
265 }
266
267 let mut si: STARTUPINFOEXW = unsafe { std::mem::zeroed() };
270 si.StartupInfo.cb = std::mem::size_of::<STARTUPINFOEXW>() as DWORD;
271 si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
272 si.StartupInfo.hStdInput = stdin.as_raw();
273 si.StartupInfo.hStdOutput = stdout.as_raw();
274 si.StartupInfo.hStdError = stderr.as_raw();
275 si.lpAttributeList = attr_list;
276
277 let mut pi: PROCESS_INFORMATION = unsafe { std::mem::zeroed() };
278
279 let mut flags: DWORD = EXTENDED_STARTUPINFO_PRESENT
280 | DETACHED_PROCESS
281 | CREATE_NEW_PROCESS_GROUP
282 | CREATE_NO_WINDOW;
283 if env_block.is_some() {
284 flags |= CREATE_UNICODE_ENVIRONMENT;
285 }
286
287 let cwd_ptr = cwd_w
288 .as_ref()
289 .map(|v| v.as_ptr())
290 .unwrap_or(std::ptr::null());
291 let env_ptr = env_block
292 .as_ref()
293 .map(|v| v.as_ptr() as *mut std::ffi::c_void)
294 .unwrap_or(std::ptr::null_mut());
295
296 let ok = unsafe {
297 CreateProcessW(
298 std::ptr::null(),
299 cmdline.as_mut_ptr(),
300 std::ptr::null_mut(),
301 std::ptr::null_mut(),
302 TRUE as BOOL, flags,
304 env_ptr,
305 cwd_ptr,
306 &mut si.StartupInfo,
307 &mut pi,
308 )
309 };
310 let err = if ok == FALSE {
311 Some(std::io::Error::last_os_error())
312 } else {
313 None
314 };
315 unsafe {
316 DeleteProcThreadAttributeList(attr_list);
317 }
318 if let Some(err) = err {
319 return Err(err);
320 }
321
322 unsafe {
324 CloseHandle(pi.hThread);
325 }
326
327 Ok(super::SanitizedChild {
328 pid: pi.dwProcessId,
329 handle: OwnedHandle(pi.hProcess),
330 })
331 }
332
333 pub fn terminate(handle: &OwnedHandle) -> std::io::Result<()> {
334 let ok = unsafe { TerminateProcess(handle.as_raw(), 1) };
335 if ok == FALSE {
336 return Err(std::io::Error::last_os_error());
337 }
338 Ok(())
339 }
340
341 pub fn wait(handle: &OwnedHandle) -> std::io::Result<i32> {
342 let rc = unsafe { WaitForSingleObject(handle.as_raw(), INFINITE) };
343 if rc != WAIT_OBJECT_0 {
344 return Err(std::io::Error::last_os_error());
345 }
346 let mut code: DWORD = 0;
347 let ok = unsafe { GetExitCodeProcess(handle.as_raw(), &mut code as *mut DWORD) };
348 if ok == FALSE {
349 return Err(std::io::Error::last_os_error());
350 }
351 Ok(code as i32)
352 }
353
354 pub fn try_wait(handle: &OwnedHandle) -> std::io::Result<Option<i32>> {
355 let mut code: DWORD = 0;
356 let ok = unsafe { GetExitCodeProcess(handle.as_raw(), &mut code as *mut DWORD) };
357 if ok == FALSE {
358 return Err(std::io::Error::last_os_error());
359 }
360 if code == STILL_ACTIVE {
361 Ok(None)
362 } else {
363 Ok(Some(code as i32))
364 }
365 }
366
367 fn build_command_line<'a>(program: &OsStr, args: impl Iterator<Item = &'a OsStr>) -> Vec<u16> {
368 let mut s = String::new();
369 s.push_str("e(&program.to_string_lossy()));
370 for a in args {
371 s.push(' ');
372 s.push_str("e(&a.to_string_lossy()));
373 }
374 OsStr::new(&s)
375 .encode_wide()
376 .chain(std::iter::once(0))
377 .collect()
378 }
379
380 fn quote(arg: &str) -> String {
382 if !arg.is_empty()
383 && !arg
384 .chars()
385 .any(|c| matches!(c, ' ' | '\t' | '\n' | '\x0b' | '"'))
386 {
387 return arg.to_string();
388 }
389 let mut out = String::from("\"");
390 let chars: Vec<char> = arg.chars().collect();
391 let mut i = 0;
392 while i < chars.len() {
393 let mut nbs = 0;
394 while i < chars.len() && chars[i] == '\\' {
395 nbs += 1;
396 i += 1;
397 }
398 if i == chars.len() {
399 for _ in 0..(nbs * 2) {
400 out.push('\\');
401 }
402 break;
403 } else if chars[i] == '"' {
404 for _ in 0..(nbs * 2 + 1) {
405 out.push('\\');
406 }
407 out.push('"');
408 } else {
409 for _ in 0..nbs {
410 out.push('\\');
411 }
412 out.push(chars[i]);
413 }
414 i += 1;
415 }
416 out.push('"');
417 out
418 }
419
420 fn build_env_block(overrides: Vec<(OsString, Option<OsString>)>) -> Vec<u16> {
421 use std::collections::BTreeMap;
422 let mut env: BTreeMap<OsString, OsString> = BTreeMap::new();
425 for (k, v) in std::env::vars_os() {
426 env.insert(k, v);
427 }
428 for (k, v) in overrides {
429 match v {
430 Some(val) => {
431 env.insert(k, val);
432 }
433 None => {
434 env.remove(&k);
435 }
436 }
437 }
438 let mut block: Vec<u16> = Vec::new();
439 for (k, v) in env {
440 block.extend(k.encode_wide());
441 block.push(b'=' as u16);
442 block.extend(v.encode_wide());
443 block.push(0);
444 }
445 block.push(0);
447 block
448 }
449}
450
451#[cfg(unix)]
454mod unix {
455 use std::process::Command;
456
457 pub fn spawn(command: &mut Command) -> std::io::Result<super::SanitizedChild> {
458 use std::os::unix::process::CommandExt;
459 use std::process::Stdio;
460
461 command
463 .stdin(Stdio::null())
464 .stdout(Stdio::null())
465 .stderr(Stdio::null());
466
467 unsafe {
468 command.pre_exec(|| {
469 if libc::setsid() == -1 {
471 }
474 close_extra_fds();
475 Ok(())
476 });
477 }
478
479 let child = command.spawn()?;
480 let pid = child.id();
481 Ok(super::SanitizedChild { pid, child })
482 }
483
484 unsafe fn close_extra_fds() {
496 #[cfg(target_os = "linux")]
498 {
499 #[cfg(any(
501 target_arch = "x86_64",
502 target_arch = "aarch64",
503 target_arch = "x86",
504 target_arch = "arm",
505 target_arch = "riscv64",
506 target_arch = "powerpc64",
507 ))]
508 {
509 const SYS_CLOSE_RANGE: libc::c_long = 436;
510 let rc = libc::syscall(SYS_CLOSE_RANGE, 3u32, libc::c_uint::MAX, 0u32);
511 if rc == 0 {
512 return;
513 }
514 }
515 }
516
517 let dir = libc::opendir(c"/dev/fd".as_ptr());
519 if !dir.is_null() {
520 let dir_fd = libc::dirfd(dir);
521 loop {
522 let ent = libc::readdir(dir);
523 if ent.is_null() {
524 break;
525 }
526 let name_ptr = (*ent).d_name.as_ptr();
527 let mut fd: libc::c_int = 0;
528 let mut p = name_ptr;
529 let mut ok = false;
530 while *p != 0 {
531 let c = *p as u8;
532 if !c.is_ascii_digit() {
533 ok = false;
534 break;
535 }
536 fd = fd * 10 + (c - b'0') as libc::c_int;
537 p = p.add(1);
538 ok = true;
539 }
540 if !ok {
541 continue;
542 }
543 if fd > 2 && fd != dir_fd {
544 libc::close(fd);
545 }
546 }
547 libc::closedir(dir);
548 return;
549 }
550
551 let max = libc::sysconf(libc::_SC_OPEN_MAX);
553 let max = if max < 0 { 4096 } else { max as libc::c_int };
554 for fd in 3..max {
555 libc::close(fd);
556 }
557 }
558}
559
560#[cfg(all(test, unix))]
561mod tests {
562 use super::*;
563
564 #[test]
565 fn sanitized_child_unix_holds_child() {
566 let _ = std::mem::size_of::<SanitizedChild>();
568 }
569}