Skip to content

Commit 3794cb7

Browse files
committed
fix a bug where cloned pipes were inheritable on Windows
There is a bug (I think it's a bug?) in libstd which makes File::try_clone return inheritable handles. We need to work around that by making the same system calls explictly, but with the inheritable flag set to false. I've filed a fix against libstd (rust-lang/rust#65316), but even if that lands we'll need to retain this workaround for all the past versions of Rust that have the bug.
1 parent 3b187a3 commit 3794cb7

File tree

2 files changed

+37
-5
lines changed

2 files changed

+37
-5
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ edition = "2018"
1313
libc = "0.2.62"
1414

1515
[target.'cfg(windows)'.dependencies]
16-
winapi = { version = "0.3.5", features = ["namedpipeapi"] }
16+
winapi = { version = "0.3.5", features = ["handleapi", "namedpipeapi", "processthreadsapi", "winnt"] }

src/windows.rs

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ use crate::PipeReader;
22
use crate::PipeWriter;
33
use std::fs::File;
44
use std::io;
5-
use std::mem::ManuallyDrop;
65
use std::os::windows::prelude::*;
76
use std::ptr;
7+
use winapi::shared::minwindef::BOOL;
88
use winapi::shared::ntdef::{HANDLE, PHANDLE};
9+
use winapi::um::handleapi::DuplicateHandle;
910
use winapi::um::namedpipeapi;
11+
use winapi::um::processthreadsapi::GetCurrentProcess;
12+
use winapi::um::winnt::DUPLICATE_SAME_ACCESS;
1013

1114
pub(crate) fn pipe() -> io::Result<(PipeReader, PipeWriter)> {
1215
let mut read_pipe: HANDLE = ptr::null_mut();
@@ -36,9 +39,38 @@ pub(crate) fn pipe() -> io::Result<(PipeReader, PipeWriter)> {
3639
}
3740

3841
pub(crate) fn dup<T: AsRawHandle>(wrapper: T) -> io::Result<File> {
39-
let handle = wrapper.as_raw_handle();
40-
let temp_file = ManuallyDrop::new(unsafe { File::from_raw_handle(handle) });
41-
temp_file.try_clone()
42+
// We rely on ("abuse") std::fs::File for a lot of descriptor/handle
43+
// operations. (For example, setting F_DUPFD_CLOEXEC on Unix is a
44+
// compatibility mess.) However, in the particular case of try_clone on
45+
// Windows, the standard library has a bug where duplicated handles end up
46+
// inheritable when they shouldn't be. See
47+
// https://github.com/rust-lang/rust/pull/65316. This leads to races where
48+
// child processes can inherit each other's handles, which tends to cause
49+
// deadlocks when the handle in question is a stdout pipe. To get that
50+
// right, we explicitly make the necessary system calls here, just like
51+
// libstd apart from that one flag.
52+
let source_handle = wrapper.as_raw_handle();
53+
let desired_access = 0; // Ignored because of DUPLICATE_SAME_ACCESS.
54+
let inherit_handle = false as BOOL; // <-- Libstd sets this to true!
55+
let options = DUPLICATE_SAME_ACCESS;
56+
let mut duplicated_handle = 0 as RawHandle;
57+
let ret = unsafe {
58+
let current_process = GetCurrentProcess();
59+
DuplicateHandle(
60+
current_process,
61+
source_handle,
62+
current_process,
63+
&mut duplicated_handle,
64+
desired_access,
65+
inherit_handle,
66+
options,
67+
)
68+
};
69+
if ret == 0 {
70+
Err(io::Error::last_os_error())
71+
} else {
72+
unsafe { Ok(File::from_raw_handle(duplicated_handle)) }
73+
}
4274
}
4375

4476
impl IntoRawHandle for PipeReader {

0 commit comments

Comments
 (0)