-
Notifications
You must be signed in to change notification settings - Fork 13.9k
Add new inherit_handles flag to CommandExt trait #115501
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Add new inherit_handles flag to CommandExt trait #115501
Conversation
This comment has been minimized.
This comment has been minimized.
Should this have a stabilization issue attached to it? |
First you should create an API change proposal to see if libs-api wants it. |
I recently changed the exact same interface and didn't need an ACP, is this really required here? |
|
Anyway, does windows have an equivalent to Inheritance is questionable in multi-threaded programs because different command-spawning actions may want to pass different files to the child. Also, how does one communicate to a child that a specific handle is available? On unix we've run into issues defining IO safety when passing ownership through the environment. Are there better ways to send handles to another process?
Alternatively, can we limit this to passing a set of well-defined handles? |
It is not necessary when creating a pseudoconsole to disable handle inheritance. Doing so does however allow us to skip the mutex we currently use.
Yes. You need to set
The reason the previous API change didn't require an ACP was because the change was already accepted under an older system (and I asked privately if this was still OK). A new API would need an ACP. |
Do you know why all of them set |
Okay I just tested it, indeed not necessary to set Would this still be a useful extension to the |
The most likely the simplest explanation is "because the example code does". But there are good reasons to disable handle inheritance when it's not needed: inheriting unnecessary handles is pointless and also prevents kernel objects from being cleaned up. In the worst case it may also allow the child process to mess with the handles it inherits, though it would have to find them first.
I think it would for the above reasons. |
I don't know if windows has the concept of confidential handles but preventing the child from acquiring those handles could improve security of spawning commands. |
Okay, just found the following list in the Processes can inherit or duplicate handles to the following types of objects:
|
@ChrisDenton should I message somebody about this in rust internals? |
You don't need to do anything other than be patient 🙂. ACPs are discussed in a weekly meeting (time permitting). |
Sorry, didn't know that. |
☔ The latest upstream changes (presumably #117285) made this pull request unmergeable. Please resolve the merge conflicts. |
Hi everyone, While handle inheritance can be selectively configured using the This effectively means that Rust programs running as PPL currently cannot spawn non-protected processes using the standard library, as the
|
I have an exam on Monday but after that I should be able to work on this. |
@pixmaip would the following suggestion aid your need? |
No, the suggestion you mentioned might be good for most usecases, but when running processes as PPL, passing a list of handles to inherit cannot be done. This is because PPL processes are inherently prevented by the system from leaking handles to non-protected processes, so Windows does not allow it. Note that setting |
That didn't actually seam to run any tests |
I'm really not an expert in this, but maybe try using |
Just tested this and it seems to provide adequate reproducibility. The equality assertion only needs to be changed from 0 to 1, as explained in the somewhat-of-a-strong-guess comment I added below. Other than that, I fixed a few minor things here and there, such as a missing semicolon, missing imports, formatting, 2021 -> 2024 edition, ... I only ran the binary obtained from a simple crate completely outside of the compiler UI tests, so there could still be some failures, but considering the relative simplicity of the test, I guess it should be fine. The module is now: // Tests `inherit_handles` by spawning a child process and checking the handle
// count of the child process to be 1.
//@ run-pass
//@ only-windows
//@ needs-subprocess
//@ edition: 2024
#![feature(windows_process_extensions_inherit_handles)]
use std::os::windows::io::AsRawHandle;
use std::os::windows::process::CommandExt;
use std::process::{Command, Stdio};
use std::{thread, time};
fn main() {
if std::env::args().skip(1).any(|s| s == "--child") {
child();
} else {
parent();
}
}
fn parent() {
let this_exe = std::env::current_exe().unwrap();
let mut child_proc = Command::new(&this_exe)
.arg("--child")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.inherit_handles(false)
.spawn()
.unwrap();
let mut handle_count = 0;
unsafe {
GetProcessHandleCount(child_proc.as_raw_handle(), &mut handle_count);
}
// Only a single handle to the PE file the process is spawned from is expected at this point.
assert_eq!(handle_count, 1);
// Cleanup.
child_proc.kill().unwrap();
child_proc.wait().unwrap();
}
// A process that stays running until killed.
fn child() {
// Don't wait forever if something goes wrong.
thread::sleep(time::Duration::from_secs(10));
}
// Windows API
mod winapi {
use std::os::windows::raw::HANDLE;
#[link(name = "kernel32")]
unsafe extern "system" {
pub fn GetProcessHandleCount(hprocess: HANDLE, pdwhandlecount: *mut u32) -> i32;
}
}
use winapi::*; With this, I guess the two testing strategies could be either merged together or done separately, if it still seems appropriate. |
Have you tested it with inherit handles set to true? Is that stable? |
Ah yes, I forgot about that. When using I also added: .stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped()) on the |
e3ad17d
to
bbaa4f5
Compare
I think this should be fine for now, it's a small feature with a relatively small surface area. @PaulDance thank you. |
@michaelvanstraten Looks good! @ChrisDenton would it be possible to run a try build with the Windows UI tests, please? |
@bors try jobs=x86_64-msvc-1,x86_64-msvc-2,i686-msvc-1,i686-msvc-2 |
@PaulDance: 🔑 Insufficient privileges: not in try users |
@bors try jobs=x86_64-msvc-1,x86_64-msvc-2,i686-msvc-1,i686-msvc-2 |
@michaelvanstraten: 🔑 Insufficient privileges: not in try users |
r? @ChrisDenton |
Requested reviewer is already assigned to this pull request. Please choose another assignee. |
@rustbot label S-waiting-on-review |
r? |
Error: Parsing assign command in comment failed: ...'' | error: specify user to assign to at >| ''... Please file an issue on GitHub at triagebot if there's a problem with this bot, or reach out on #triagebot on Zulip. |
r? libs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I've not been reviewing much lately. I'm back now though.
This looks good to me I just have a couple of nits.
} | ||
|
||
// Only a single handle to the PE file the process is spawned from is expected at this point. | ||
assert_eq!(handle_count, 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm slightly worried that the Windows loader, CRT or even the rust runtime could open handles. Could you perhaps spawn the child process twice (one where inherit_handles
is true
and one where it is false
) and compare their handle counts? I think that would be more robust.
Oh and please do check GetProcessHandleCount
doesn't error. While one would hope that it doesn't set the handle count in that case, this isn't guaranteed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like so:
// Tests `inherit_handles` by spawning a child process and checking its handle
// count to be greater than when not setting the option.
//@ run-pass
//@ only-windows
//@ needs-subprocess
//@ edition: 2024
#![feature(windows_process_extensions_inherit_handles)]
use std::os::windows::io::AsRawHandle;
use std::os::windows::process::CommandExt;
use std::process::{Command, Stdio};
use std::time::Duration;
use std::{env, io, thread};
fn main() {
if std::env::args().skip(1).any(|s| s == "--child") {
child();
} else {
parent();
}
}
fn parent() {
let with_inherit_count = child_handle_count(true);
let without_inherit_count = child_handle_count(false);
// Only compare the two values instead of only expecting a hard 1 for
// robustness, although only 1 has ever been observed here.
assert!(
with_inherit_count > without_inherit_count,
"Child process handle count unexpectedly smaller when inheriting handles compared to when not: {} <= {}",
with_inherit_count,
without_inherit_count,
);
}
/// Spawns the current program as a child process and returns its handle count.
fn child_handle_count(inherit_handles: bool) -> u32 {
let mut child_proc = Command::new(&env::current_exe().unwrap())
.arg("--child")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.inherit_handles(inherit_handles)
.spawn()
.unwrap();
let mut handle_count = 0;
let ret = unsafe { GetProcessHandleCount(child_proc.as_raw_handle(), &raw mut handle_count) };
assert_ne!(
ret,
0,
"GetProcessHandleCount failed: {:?}",
io::Error::last_os_error(),
);
// Cleanup.
child_proc.kill().unwrap();
child_proc.wait().unwrap();
handle_count
}
/// A process that stays running until killed.
fn child() {
// Don't wait forever if something goes wrong.
thread::sleep(Duration::from_secs(10));
}
// Windows API
mod winapi {
use std::os::windows::raw::HANDLE;
#[link(name = "kernel32")]
unsafe extern "system" {
pub fn GetProcessHandleCount(hprocess: HANDLE, pdwhandlecount: *mut u32) -> i32;
}
}
use winapi::*;
?
check GetProcessHandleCount doesn't error. While one would hope that it doesn't set the handle count in that case, this isn't guaranteed.
It is documented that the value is zero on error and non-zero on failure, so before the changes above, the errors were effectively checked for already, although without using io::Error::last_os_error
for clarity when making the test fail. Now it relies on that in order to check for such errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return value is documented as being zero on error. The documentation doesn't say anything about the state of pdwHandleCount
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yes, you're right; sorry, I misread that. I've updated the above comment accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ChrisDenton is right here, we should maybe use the BOOL
abstraction windows_core uses.
But let me worry about that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@PaulDance, I guess you were faster :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes :)
windows-rs
is not used too much here from what I could see, so a manual check keeps things dependency-less while sufficient I would say.
I'll therefore let you integrate the above full code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've integrated the change, rebasing now.
bbaa4f5
to
c968a00
Compare
This comment has been minimized.
This comment has been minimized.
This patch adds a new flag to the [`CommandExt`](1) trait to set whether to inherit the handles of the calling process (2) on Windows systems. ACP: rust-lang/libs-team#264 [1]: https://doc.rust-lang.org/stable/std/os/windows/process/trait.CommandExt.html [2]: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw
c968a00
to
24b0c27
Compare
This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
This PR adds a new flag to the
CommandExt
trait to set whether to inherit the handles of the calling process ([ref][1]).This is necessary when, for example, spawning a process with a
pseudoconsole
attached.r? @ChrisDenton
ACP: rust-lang/libs-team#264
[1]: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw