Skip to content

Commit d4d6521

Browse files
Add mechanism to recover windows pipe handles (#388)
* generalize windows named pipe creation. * add recover_handles method. * add test harness for recovering handles. * cleanup docs. * add more test coverage. * fix eluded liftime. * Update src/platform/windows/mod.rs Co-authored-by: Ngo Iok Ui (Wu Yu Wei) <[email protected]> Signed-off-by: Tensor-Programming <[email protected]> --------- Signed-off-by: Tensor-Programming <[email protected]> Co-authored-by: Ngo Iok Ui (Wu Yu Wei) <[email protected]>
1 parent 84ec7ed commit d4d6521

File tree

2 files changed

+585
-42
lines changed

2 files changed

+585
-42
lines changed

src/platform/windows/mod.rs

+104-42
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ use windows::{
3737
},
3838
Storage::FileSystem::{
3939
CreateFileA, ReadFile, WriteFile, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_OVERLAPPED,
40-
FILE_GENERIC_WRITE, FILE_SHARE_MODE, OPEN_EXISTING, PIPE_ACCESS_INBOUND,
40+
FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_SHARE_MODE, OPEN_EXISTING,
41+
PIPE_ACCESS_DUPLEX,
4142
},
4243
System::{
4344
Memory::{
@@ -61,8 +62,12 @@ use windows::{
6162
};
6263

6364
mod aliased_cell;
65+
6466
use self::aliased_cell::AliasedCell;
6567

68+
#[cfg(test)]
69+
mod tests;
70+
6671
lazy_static! {
6772
static ref CURRENT_PROCESS_ID: u32 = unsafe { GetCurrentProcessId() };
6873
static ref CURRENT_PROCESS_HANDLE: WinHandle = WinHandle::new(unsafe { GetCurrentProcess() });
@@ -126,6 +131,32 @@ pub fn channel() -> Result<(OsIpcSender, OsIpcReceiver), WinError> {
126131
Ok((sender, receiver))
127132
}
128133

134+
/// Unify the creation of sender and receiver duplex pipes to allow for either to be spawned first.
135+
/// Requires the use of a duplex and therefore lets both sides read and write.
136+
unsafe fn create_duplex(pipe_name: &CString) -> Result<HANDLE, WinError> {
137+
CreateFileA(
138+
PCSTR::from_raw(pipe_name.as_ptr() as *const u8),
139+
FILE_GENERIC_WRITE.0 | FILE_GENERIC_READ.0,
140+
FILE_SHARE_MODE(0),
141+
None, // lpSecurityAttributes
142+
OPEN_EXISTING,
143+
FILE_ATTRIBUTE_NORMAL,
144+
None,
145+
)
146+
.or(CreateNamedPipeA(
147+
PCSTR::from_raw(pipe_name.as_ptr() as *const u8),
148+
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
149+
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_REJECT_REMOTE_CLIENTS,
150+
// 1 max instance of this pipe
151+
1,
152+
// out/in buffer sizes
153+
0,
154+
PIPE_BUFFER_SIZE as u32,
155+
0, // default timeout for WaitNamedPipe (0 == 50ms as default)
156+
None,
157+
))
158+
}
159+
129160
struct MessageHeader {
130161
data_len: u32,
131162
oob_len: u32,
@@ -144,7 +175,7 @@ struct Message<'data> {
144175
}
145176

146177
impl<'data> Message<'data> {
147-
fn from_bytes(bytes: &'data [u8]) -> Option<Message> {
178+
fn from_bytes(bytes: &'data [u8]) -> Option<Message<'data>> {
148179
if bytes.len() < mem::size_of::<MessageHeader>() {
149180
return None;
150181
}
@@ -174,14 +205,11 @@ impl<'data> Message<'data> {
174205

175206
fn oob_data(&self) -> Option<OutOfBandMessage> {
176207
if self.oob_len > 0 {
177-
let oob = bincode::deserialize::<OutOfBandMessage>(self.oob_bytes())
208+
let mut oob = bincode::deserialize::<OutOfBandMessage>(self.oob_bytes())
178209
.expect("Failed to deserialize OOB data");
179-
if oob.target_process_id != *CURRENT_PROCESS_ID {
180-
panic!("Windows IPC channel received handles intended for pid {}, but this is pid {}. \
181-
This likely happened because a receiver was transferred while it had outstanding data \
182-
that contained a channel or shared memory in its pipe. \
183-
This isn't supported in the Windows implementation.",
184-
oob.target_process_id, *CURRENT_PROCESS_ID);
210+
if let Err(e) = oob.recover_handles() {
211+
win32_trace!("Failed to recover handles: {:?}", e);
212+
return None;
185213
}
186214
Some(oob)
187215
} else {
@@ -202,18 +230,7 @@ impl<'data> Message<'data> {
202230
/// in another channel's buffer when that channel got transferred to another
203231
/// process. On Windows, we duplicate handles on the sender side to a specific
204232
/// receiver. If the wrong receiver gets it, those handles are not valid.
205-
///
206-
/// TODO(vlad): We could attempt to recover from the above situation by
207-
/// duplicating from the intended target process to ourselves (the receiver).
208-
/// That would only work if the intended process a) still exists; b) can be
209-
/// opened by the receiver with handle dup privileges. Another approach
210-
/// could be to use a separate dedicated process intended purely for handle
211-
/// passing, though that process would need to be global to any processes
212-
/// amongst which you want to share channels or connect one-shot servers to.
213-
/// There may be a system process that we could use for this purpose, but
214-
/// I haven't found one -- and in the system process case, we'd need to ensure
215-
/// that we don't leak the handles (e.g. dup a handle to the system process,
216-
/// and then everything dies -- we don't want those resources to be leaked).
233+
/// These handles are recovered by the `recover_handles` method.
217234
#[derive(Debug)]
218235
struct OutOfBandMessage {
219236
target_process_id: u32,
@@ -237,6 +254,70 @@ impl OutOfBandMessage {
237254
|| !self.shmem_handles.is_empty()
238255
|| self.big_data_receiver_handle.is_some()
239256
}
257+
258+
/// Recover handles that are no longer valid in the current process via duplication.
259+
/// Duplicates the handle from the target process to the current process.
260+
fn recover_handles(&mut self) -> Result<(), WinError> {
261+
// get current process id and target process.
262+
let current_process = unsafe { GetCurrentProcess() };
263+
let target_process =
264+
unsafe { OpenProcess(PROCESS_DUP_HANDLE, false, self.target_process_id)? };
265+
266+
// Duplicate channel handles.
267+
for handle in &mut self.channel_handles {
268+
let mut new_handle = INVALID_HANDLE_VALUE;
269+
unsafe {
270+
DuplicateHandle(
271+
target_process,
272+
HANDLE(*handle as _),
273+
current_process,
274+
&mut new_handle,
275+
0,
276+
false,
277+
DUPLICATE_SAME_ACCESS,
278+
)?;
279+
}
280+
*handle = new_handle.0 as isize;
281+
}
282+
283+
// Duplicate any shmem handles.
284+
for (handle, _) in &mut self.shmem_handles {
285+
let mut new_handle = INVALID_HANDLE_VALUE;
286+
unsafe {
287+
DuplicateHandle(
288+
target_process,
289+
HANDLE(*handle as _),
290+
current_process,
291+
&mut new_handle,
292+
0,
293+
false,
294+
DUPLICATE_SAME_ACCESS,
295+
)?;
296+
}
297+
*handle = new_handle.0 as isize;
298+
}
299+
300+
// Duplicate any big data receivers.
301+
if let Some((handle, _)) = &mut self.big_data_receiver_handle {
302+
let mut new_handle = INVALID_HANDLE_VALUE;
303+
unsafe {
304+
DuplicateHandle(
305+
target_process,
306+
HANDLE(*handle as _),
307+
current_process,
308+
&mut new_handle,
309+
0,
310+
false,
311+
DUPLICATE_SAME_ACCESS,
312+
)?;
313+
}
314+
*handle = new_handle.0 as isize;
315+
}
316+
317+
// Close process handle.
318+
unsafe { CloseHandle(target_process)? };
319+
Ok(())
320+
}
240321
}
241322

242323
impl serde::Serialize for OutOfBandMessage {
@@ -1071,18 +1152,7 @@ impl OsIpcReceiver {
10711152
fn new_named(pipe_name: &CString) -> Result<OsIpcReceiver, WinError> {
10721153
unsafe {
10731154
// create the pipe server
1074-
let handle = CreateNamedPipeA(
1075-
PCSTR::from_raw(pipe_name.as_ptr() as *const u8),
1076-
PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
1077-
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_REJECT_REMOTE_CLIENTS,
1078-
// 1 max instance of this pipe
1079-
1,
1080-
// out/in buffer sizes
1081-
0,
1082-
PIPE_BUFFER_SIZE as u32,
1083-
0, // default timeout for WaitNamedPipe (0 == 50ms as default)
1084-
None,
1085-
)?;
1155+
let handle = create_duplex(pipe_name)?;
10861156

10871157
Ok(OsIpcReceiver {
10881158
reader: RefCell::new(MessageReader::new(WinHandle::new(handle))),
@@ -1253,15 +1323,7 @@ impl OsIpcSender {
12531323
/// Connect to a pipe server.
12541324
fn connect_named(pipe_name: &CString) -> Result<OsIpcSender, WinError> {
12551325
unsafe {
1256-
let handle = CreateFileA(
1257-
PCSTR::from_raw(pipe_name.as_ptr() as *const u8),
1258-
FILE_GENERIC_WRITE.0,
1259-
FILE_SHARE_MODE(0),
1260-
None, // lpSecurityAttributes
1261-
OPEN_EXISTING,
1262-
FILE_ATTRIBUTE_NORMAL,
1263-
None,
1264-
)?;
1326+
let handle = create_duplex(pipe_name)?;
12651327

12661328
win32_trace!("[c {:?}] connect_to_server success", handle);
12671329

0 commit comments

Comments
 (0)