Skip to content

Commit 5ddfa2e

Browse files
committed
Add windows::process::Command::inherit_handles
The API limits handles to inherit. The implementation follows the "Programmatically controlling which handles are inherited by new processes in Win32" blog from Microsoft: https://devblogs.microsoft.com/oldnewthing/20111216-00/?p=8873 If the API is not called, the old behavior of inheriting all inheritable handles is not changed.
1 parent 8e21bd0 commit 5ddfa2e

File tree

4 files changed

+247
-2
lines changed

4 files changed

+247
-2
lines changed

Diff for: library/std/src/process.rs

+87
Original file line numberDiff line numberDiff line change
@@ -2104,6 +2104,93 @@ mod tests {
21042104
assert!(events > 0);
21052105
}
21062106

2107+
#[test]
2108+
#[cfg(windows)]
2109+
fn test_inherit_handles() {
2110+
use crate::io;
2111+
use crate::os::windows::process::CommandExt;
2112+
use crate::sys::pipe::anon_pipe;
2113+
2114+
// Count handles of a child process using PowerShell.
2115+
fn count_child_handles(f: impl FnOnce(&mut Command) -> &mut Command) -> io::Result<usize> {
2116+
let mut command = Command::new("powershell");
2117+
let command = command.args(&[
2118+
"-Command",
2119+
"Get-Process -Id $PID | Format-Table -HideTableHeaders Handles",
2120+
]);
2121+
let out = f(command).output()?.stdout;
2122+
String::from_utf8_lossy(&out)
2123+
.trim()
2124+
.parse::<usize>()
2125+
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
2126+
}
2127+
2128+
// The exact count is unstable because tests run in other threads might
2129+
// create inheritable handles, and PowerShell is a GC runtime.
2130+
// Increase epsilon if the test becomes flaky.
2131+
let epsilon = 25;
2132+
let base_count = count_child_handles(|c| c).unwrap();
2133+
2134+
// Create `n` inheritable pipes.
2135+
let n = epsilon * 6;
2136+
let pipes: Vec<_> = (1..n).map(|_| anon_pipe(true, true).unwrap()).collect();
2137+
2138+
// Without `inherit_handles`, all pipes are inherited.
2139+
let inherit_count = count_child_handles(|c| c).unwrap();
2140+
assert!(
2141+
inherit_count > base_count + n - epsilon,
2142+
"pipes do not seem to be inherited (base: {}, inherit: {}, n: {})",
2143+
base_count,
2144+
inherit_count,
2145+
n
2146+
);
2147+
2148+
// With `inherit_handles`, none of the pipes are inherited.
2149+
let inherit_count = count_child_handles(|c| c.inherit_handles(vec![])).unwrap();
2150+
assert!(
2151+
inherit_count < base_count + epsilon,
2152+
"pipe inheritance does not seem to be limited (base: {}, inherit: {}, n: {})",
2153+
base_count,
2154+
inherit_count,
2155+
n
2156+
);
2157+
2158+
// Use `inherit_handles` to inherit half of the pipes.
2159+
let half = n / 2;
2160+
let handles: Vec<_> = pipes.iter().take(half).map(|p| p.theirs.handle().raw()).collect();
2161+
let inherit_count = count_child_handles(move |c| c.inherit_handles(handles)).unwrap();
2162+
assert!(
2163+
inherit_count > base_count + half - epsilon,
2164+
"pipe inheritance seems over-limited (base: {}, inherit: {}, n: {}, half: {})",
2165+
base_count,
2166+
inherit_count,
2167+
n,
2168+
half
2169+
);
2170+
assert!(
2171+
inherit_count < base_count + half + epsilon,
2172+
"pipe inheritance seems under-limited (base: {}, inherit: {}, n: {}, half: {})",
2173+
base_count,
2174+
inherit_count,
2175+
n,
2176+
half
2177+
);
2178+
2179+
// Call `inherit_handles` multiple times to inherit all pipes.
2180+
let handles: Vec<_> = pipes.iter().map(|p| p.theirs.handle().raw()).collect();
2181+
let inherit_count = count_child_handles(move |c| {
2182+
handles.into_iter().fold(c, |c, h| c.inherit_handles(vec![h]))
2183+
})
2184+
.unwrap();
2185+
assert!(
2186+
inherit_count > base_count + n - epsilon,
2187+
"pipe inheritance seems over-limited (base: {}, inherit: {}, n: {})",
2188+
base_count,
2189+
inherit_count,
2190+
n,
2191+
);
2192+
}
2193+
21072194
#[test]
21082195
fn test_command_implements_send_sync() {
21092196
fn take_send_sync_type<T: Send + Sync>(_: T) {}

Diff for: library/std/src/sys/windows/c.rs

+35
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub use self::EXCEPTION_DISPOSITION::*;
1313
pub use self::FILE_INFO_BY_HANDLE_CLASS::*;
1414

1515
pub type DWORD = c_ulong;
16+
pub type DWORD_PTR = ULONG_PTR;
1617
pub type HANDLE = LPVOID;
1718
pub type HINSTANCE = HANDLE;
1819
pub type HMODULE = HINSTANCE;
@@ -39,6 +40,7 @@ pub type LPCWSTR = *const WCHAR;
3940
pub type LPDWORD = *mut DWORD;
4041
pub type LPHANDLE = *mut HANDLE;
4142
pub type LPOVERLAPPED = *mut OVERLAPPED;
43+
pub type LPPROC_THREAD_ATTRIBUTE_LIST = *mut PROC_THREAD_ATTRIBUTE_LIST;
4244
pub type LPPROCESS_INFORMATION = *mut PROCESS_INFORMATION;
4345
pub type LPSECURITY_ATTRIBUTES = *mut SECURITY_ATTRIBUTES;
4446
pub type LPSTARTUPINFO = *mut STARTUPINFO;
@@ -56,7 +58,9 @@ pub type LPWSAOVERLAPPED_COMPLETION_ROUTINE = *mut c_void;
5658

5759
pub type PCONDITION_VARIABLE = *mut CONDITION_VARIABLE;
5860
pub type PLARGE_INTEGER = *mut c_longlong;
61+
pub type PSIZE_T = *mut ULONG_PTR;
5962
pub type PSRWLOCK = *mut SRWLOCK;
63+
pub type PVOID = *mut c_void;
6064

6165
pub type SOCKET = crate::os::windows::raw::SOCKET;
6266
pub type socklen_t = c_int;
@@ -101,6 +105,8 @@ pub const SECURITY_SQOS_PRESENT: DWORD = 0x00100000;
101105

102106
pub const FIONBIO: c_ulong = 0x8004667e;
103107

108+
pub const PROC_THREAD_ATTRIBUTE_HANDLE_LIST: DWORD_PTR = 0x00020002;
109+
104110
#[repr(C)]
105111
#[derive(Copy)]
106112
pub struct WIN32_FIND_DATAW {
@@ -217,6 +223,7 @@ pub const CONDITION_VARIABLE_INIT: CONDITION_VARIABLE = CONDITION_VARIABLE { ptr
217223
pub const SRWLOCK_INIT: SRWLOCK = SRWLOCK { ptr: ptr::null_mut() };
218224

219225
pub const DETACHED_PROCESS: DWORD = 0x00000008;
226+
pub const EXTENDED_STARTUPINFO_PRESENT: DWORD = 0x00080000;
220227
pub const CREATE_NEW_PROCESS_GROUP: DWORD = 0x00000200;
221228
pub const CREATE_UNICODE_ENVIRONMENT: DWORD = 0x00000400;
222229
pub const STARTF_USESTDHANDLES: DWORD = 0x00000100;
@@ -483,6 +490,11 @@ pub struct SECURITY_ATTRIBUTES {
483490
pub bInheritHandle: BOOL,
484491
}
485492

493+
#[repr(C)]
494+
pub struct PROC_THREAD_ATTRIBUTE_LIST {
495+
pub dummy: *mut c_void,
496+
}
497+
486498
#[repr(C)]
487499
pub struct PROCESS_INFORMATION {
488500
pub hProcess: HANDLE,
@@ -513,6 +525,12 @@ pub struct STARTUPINFO {
513525
pub hStdError: HANDLE,
514526
}
515527

528+
#[repr(C)]
529+
pub struct STARTUPINFOEX {
530+
pub StartupInfo: STARTUPINFO,
531+
pub lpAttributeList: LPPROC_THREAD_ATTRIBUTE_LIST,
532+
}
533+
516534
#[repr(C)]
517535
pub struct SOCKADDR {
518536
pub sa_family: ADDRESS_FAMILY,
@@ -887,6 +905,23 @@ extern "system" {
887905
lpUsedDefaultChar: LPBOOL,
888906
) -> c_int;
889907

908+
pub fn InitializeProcThreadAttributeList(
909+
lpAttributeList: LPPROC_THREAD_ATTRIBUTE_LIST,
910+
dwAttributeCount: DWORD,
911+
dwFlags: DWORD,
912+
lpSize: PSIZE_T,
913+
) -> BOOL;
914+
pub fn UpdateProcThreadAttribute(
915+
lpAttributeList: LPPROC_THREAD_ATTRIBUTE_LIST,
916+
dwFlags: DWORD,
917+
Attribute: DWORD_PTR,
918+
lpValue: PVOID,
919+
cbSize: SIZE_T,
920+
lpPreviousValue: PVOID,
921+
lpReturnSize: PSIZE_T,
922+
) -> BOOL;
923+
pub fn DeleteProcThreadAttributeList(lpAttributeList: LPPROC_THREAD_ATTRIBUTE_LIST);
924+
890925
pub fn closesocket(socket: SOCKET) -> c_int;
891926
pub fn recv(socket: SOCKET, buf: *mut c_void, len: c_int, flags: c_int) -> c_int;
892927
pub fn send(socket: SOCKET, buf: *const c_void, len: c_int, flags: c_int) -> c_int;

Diff for: library/std/src/sys/windows/ext/process.rs

+30
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,28 @@ pub trait CommandExt {
102102
/// [1]: https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
103103
#[stable(feature = "windows_process_extensions", since = "1.16.0")]
104104
fn creation_flags(&mut self, flags: u32) -> &mut process::Command;
105+
106+
/// Specifies additional [inheritable handles][1] for `CreateProcess`.
107+
///
108+
/// Handles must be created as inheritable and must not be pseudo
109+
/// such as those returned by the `GetCurrentProcess`.
110+
///
111+
/// Handles for stdio redirection are always inherited. Handles are
112+
/// passed via `STARTUPINFOEX`. Process creation flags will be ORed
113+
/// by `EXTENDED_STARTUPINFO_PRESENT` automatically.
114+
///
115+
/// Calling this function multiple times is equivalent to calling
116+
/// one time with a chained iterator.
117+
///
118+
/// If this function is not called, all inheritable handles will be
119+
/// inherited. Note this may change in the future.
120+
///
121+
/// [1]: https://docs.microsoft.com/en-us/windows/win32/procthread/inheritance#inheriting-handles
122+
#[unstable(feature = "windows_process_inherit_handles", issue = "none")]
123+
fn inherit_handles(
124+
&mut self,
125+
handles: impl IntoIterator<Item = RawHandle>,
126+
) -> &mut process::Command;
105127
}
106128

107129
#[stable(feature = "windows_process_extensions", since = "1.16.0")]
@@ -110,4 +132,12 @@ impl CommandExt for process::Command {
110132
self.as_inner_mut().creation_flags(flags);
111133
self
112134
}
135+
136+
fn inherit_handles(
137+
&mut self,
138+
handles: impl IntoIterator<Item = RawHandle>,
139+
) -> &mut process::Command {
140+
self.as_inner_mut().inherit_handles(handles.into_iter().collect());
141+
self
142+
}
113143
}

Diff for: library/std/src/sys/windows/process.rs

+95-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::fs;
1010
use crate::io::{self, Error, ErrorKind};
1111
use crate::mem;
1212
use crate::os::windows::ffi::OsStrExt;
13+
use crate::os::windows::io::RawHandle;
1314
use crate::path::Path;
1415
use crate::ptr;
1516
use crate::sys::c;
@@ -72,11 +73,17 @@ pub struct Command {
7273
cwd: Option<OsString>,
7374
flags: u32,
7475
detach: bool, // not currently exposed in std::process
76+
inherit_handles: Option<SendHandles>,
7577
stdin: Option<Stdio>,
7678
stdout: Option<Stdio>,
7779
stderr: Option<Stdio>,
7880
}
7981

82+
struct SendHandles(Vec<RawHandle>);
83+
84+
unsafe impl Send for SendHandles {}
85+
unsafe impl Sync for SendHandles {}
86+
8087
pub enum Stdio {
8188
Inherit,
8289
Null,
@@ -94,6 +101,13 @@ struct DropGuard<'a> {
94101
lock: &'a Mutex,
95102
}
96103

104+
#[derive(Default)]
105+
struct ProcAttributeList {
106+
buf: Vec<u8>,
107+
inherit_handles: Vec<RawHandle>,
108+
initialized: bool,
109+
}
110+
97111
impl Command {
98112
pub fn new(program: &OsStr) -> Command {
99113
Command {
@@ -103,6 +117,7 @@ impl Command {
103117
cwd: None,
104118
flags: 0,
105119
detach: false,
120+
inherit_handles: None,
106121
stdin: None,
107122
stdout: None,
108123
stderr: None,
@@ -130,6 +145,12 @@ impl Command {
130145
pub fn creation_flags(&mut self, flags: u32) {
131146
self.flags = flags;
132147
}
148+
pub fn inherit_handles(&mut self, handles: Vec<RawHandle>) {
149+
match self.inherit_handles {
150+
Some(ref mut list) => list.0.extend(handles),
151+
None => self.inherit_handles = Some(SendHandles(handles)),
152+
}
153+
}
133154

134155
pub fn spawn(
135156
&mut self,
@@ -156,7 +177,11 @@ impl Command {
156177
None
157178
});
158179

159-
let mut si = zeroed_startupinfo();
180+
let mut si_ex = c::STARTUPINFOEX {
181+
StartupInfo: zeroed_startupinfo(),
182+
lpAttributeList: ptr::null_mut(),
183+
};
184+
let si = &mut si_ex.StartupInfo;
160185
si.cb = mem::size_of::<c::STARTUPINFO>() as c::DWORD;
161186
si.dwFlags = c::STARTF_USESTDHANDLES;
162187

@@ -199,6 +224,20 @@ impl Command {
199224
si.hStdOutput = stdout.raw();
200225
si.hStdError = stderr.raw();
201226

227+
// Limit handles to inherit.
228+
// See https://devblogs.microsoft.com/oldnewthing/20111216-00/?p=8873
229+
let mut proc_attribute_list = ProcAttributeList::default();
230+
if let Some(SendHandles(ref handles)) = self.inherit_handles {
231+
// Unconditionally inherit stdio redirection handles.
232+
let mut inherit_handles = handles.clone();
233+
inherit_handles.extend_from_slice(&[stdin.raw(), stdout.raw(), stderr.raw()]);
234+
proc_attribute_list.init(1)?;
235+
proc_attribute_list.set_handle_list(inherit_handles)?;
236+
si.cb = mem::size_of::<c::STARTUPINFOEX>() as c::DWORD;
237+
si_ex.lpAttributeList = proc_attribute_list.ptr();
238+
flags |= c::EXTENDED_STARTUPINFO_PRESENT;
239+
}
240+
202241
unsafe {
203242
cvt(c::CreateProcessW(
204243
ptr::null(),
@@ -209,7 +248,7 @@ impl Command {
209248
flags,
210249
envp,
211250
dirp,
212-
&mut si,
251+
si,
213252
&mut pi,
214253
))
215254
}?;
@@ -250,6 +289,60 @@ impl<'a> Drop for DropGuard<'a> {
250289
}
251290
}
252291

292+
impl ProcAttributeList {
293+
fn init(&mut self, attribute_count: c::DWORD) -> io::Result<()> {
294+
// Allocate
295+
let mut size: c::SIZE_T = 0;
296+
cvt(unsafe {
297+
c::InitializeProcThreadAttributeList(ptr::null_mut(), attribute_count, 0, &mut size)
298+
})
299+
.or_else(|e| match e.raw_os_error().unwrap_or_default() as c::DWORD {
300+
c::ERROR_INSUFFICIENT_BUFFER => Ok(0),
301+
_ => Err(e),
302+
})?;
303+
self.buf.resize(size as usize, 0);
304+
305+
// Initialize
306+
cvt(unsafe {
307+
c::InitializeProcThreadAttributeList(self.ptr(), attribute_count, 0, &mut size)
308+
})?;
309+
self.initialized = true;
310+
311+
Ok(())
312+
}
313+
314+
fn set_handle_list(&mut self, handles: Vec<RawHandle>) -> io::Result<()> {
315+
// Take ownership. The PROC_THREAD_ATTRIBUTE_LIST struct might
316+
// refer to the handles using pointers.
317+
self.inherit_handles = handles;
318+
cvt(unsafe {
319+
c::UpdateProcThreadAttribute(
320+
self.ptr(),
321+
0,
322+
c::PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
323+
self.inherit_handles.as_mut_ptr() as c::PVOID,
324+
self.inherit_handles.len() * mem::size_of::<c::HANDLE>(),
325+
ptr::null_mut(),
326+
ptr::null_mut(),
327+
)
328+
})?;
329+
330+
Ok(())
331+
}
332+
333+
fn ptr(&mut self) -> c::LPPROC_THREAD_ATTRIBUTE_LIST {
334+
self.buf.as_mut_ptr() as _
335+
}
336+
}
337+
338+
impl Drop for ProcAttributeList {
339+
fn drop(&mut self) {
340+
if self.initialized {
341+
unsafe { c::DeleteProcThreadAttributeList(self.ptr()) };
342+
}
343+
}
344+
}
345+
253346
impl Stdio {
254347
fn to_handle(&self, stdio_id: c::DWORD, pipe: &mut Option<AnonPipe>) -> io::Result<Handle> {
255348
match *self {

0 commit comments

Comments
 (0)