Skip to content

Conversation

@michaelvanstraten
Copy link
Contributor

@michaelvanstraten michaelvanstraten commented Sep 3, 2023

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

@rustbot rustbot added O-windows Operating system: Windows S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Sep 3, 2023
@rust-log-analyzer

This comment has been minimized.

@michaelvanstraten
Copy link
Contributor Author

Should this have a stabilization issue attached to it?

@the8472
Copy link
Member

the8472 commented Sep 3, 2023

First you should create an API change proposal to see if libs-api wants it.

@michaelvanstraten
Copy link
Contributor Author

michaelvanstraten commented Sep 3, 2023

I recently changed the exact same interface and didn't need an ACP, is this really required here?

@the8472
Copy link
Member

the8472 commented Sep 3, 2023

No, not required

Note that an ACP is not strictly required: you can just go ahead and submit a pull request with an implementation of your proposed API, with the risk of wasted effort if the library team ends up rejecting this feature. However do note that this risk is always present even if an ACP is accepted, as the library team can end up rejecting a feature in the later parts of the stabilization process.

@the8472
Copy link
Member

the8472 commented Sep 3, 2023

Anyway, does windows have an equivalent to posix_spawn_file_actions_adddup2? I.e. a way to provide a list of descriptors that should be explicitly passed to the child process rather than relying on inheritance flags on handles?

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?

This is necessary when, for example, spawning a process with a pseudoconsole attached.

Alternatively, can we limit this to passing a set of well-defined handles?

@ChrisDenton
Copy link
Member

ChrisDenton commented Sep 3, 2023

This is necessary when, for example, spawning a process with a pseudoconsole attached.

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.

Anyway, does windows have an equivalent to posix_spawn_file_actions_adddup2? I.e. a way to provide a list of descriptors that should be explicitly passed to the child process rather than relying on inheritance flags on handles?

Yes. You need to set inherit_handles to true and then set the PROC_THREAD_ATTRIBUTE_HANDLE_LIST attribute. Rust should have been doing this from the start, imho, but not sure if we can change it now.

I recently changed the exact same interface and didn't need an ACP, is this really required here?

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.

@michaelvanstraten
Copy link
Contributor Author

michaelvanstraten commented Sep 3, 2023

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.

Do you know why all of them set bInheritHandles to false? I have look at a dozenth reference implementation so far and all do so.

@michaelvanstraten
Copy link
Contributor Author

Okay I just tested it, indeed not necessary to set bInheritHandles.

Would this still be a useful extension to the CommandExt trait?

@ChrisDenton
Copy link
Member

ChrisDenton commented Sep 3, 2023

Do you know why all of them set bInheritHandles to false? I have look at a dozenth reference implementation so far and all do so.

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.

Would this still be a useful extension to the CommandExt trait?

I think it would for the above reasons.

@michaelvanstraten
Copy link
Contributor Author

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.

@michaelvanstraten
Copy link
Contributor Author

Okay, just found the following list in the windows documentation:

Processes can inherit or duplicate handles to the following types of objects:

  • Access Token
  • Communications device
  • Console input
  • Console screen buffer
  • Desktop
  • Directory
  • Event
  • File
  • File mapping
  • Job
  • Mailslot
  • Mutex
  • Pipe
  • Process
  • Registry key
  • Semaphore
  • Socket
  • Thread
  • Timer

@michaelvanstraten
Copy link
Contributor Author

rust-lang/libs-team#264

@michaelvanstraten
Copy link
Contributor Author

@ChrisDenton should I message somebody about this in rust internals?

@ChrisDenton
Copy link
Member

You don't need to do anything other than be patient 🙂. ACPs are discussed in a weekly meeting (time permitting).

@michaelvanstraten
Copy link
Contributor Author

michaelvanstraten commented Sep 5, 2023

ACPs are discussed in a weekly meeting (time permitting).

Sorry, didn't know that.

@Dylan-DPC Dylan-DPC added S-waiting-on-ACP Status: PR has an ACP and is waiting for the ACP to complete. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Nov 6, 2023
@bors
Copy link
Collaborator

bors commented Jan 13, 2024

☔ The latest upstream changes (presumably #117285) made this pull request unmergeable. Please resolve the merge conflicts.

@bors bors added the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Jan 13, 2024
@Dylan-DPC Dylan-DPC removed the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Feb 14, 2024
@pixmaip
Copy link

pixmaip commented Sep 25, 2024

Hi everyone,
I am unearthing this discussion as this proposed API can be mandatory in some cases.

While handle inheritance can be selectively configured using the PROC_THREAD_ATTRIBUTE_HANDLE_LIST attribute, in the case of protected processes, the bInheritHandles parameter still needs to be set to FALSE in all cases, otherwise the CreateProcess call will fail.

This effectively means that Rust programs running as PPL currently cannot spawn non-protected processes using the standard library, as the spawn will always fail, as deccribed in the Microsoft documentation of CreateProcess:

Protected Process Light (PPL) processes: The generic handle inheritance is blocked when a PPL process creates a non-PPL process since PROCESS_DUP_HANDLE is not allowed from a non-PPL process to a PPL process. See Process Security and Access Rights

@michaelvanstraten
Copy link
Contributor Author

I have an exam on Monday but after that I should be able to work on this.

@michaelvanstraten
Copy link
Contributor Author

michaelvanstraten commented Oct 1, 2024

@pixmaip would the following suggestion aid your need?
rust-lang/libs-team#264 (comment)

@pixmaip
Copy link

pixmaip commented Oct 1, 2024

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.
For the CreateProcess API to succeed, you need to have bInheritHandles set to FALSE.

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 bInheritHandles to FALSE will automatically prevent standard handles to work properly, meaning that stdin will not be passed through to the child process, and stdout/err will not output any data. The only possible "interaction" with the child process is through its exit status.

@rustbot
Copy link
Collaborator

rustbot commented Oct 7, 2025

Requested reviewer is already assigned to this pull request.

Please choose another assignee.

@PaulDance
Copy link
Contributor

@rustbot label S-waiting-on-review

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Oct 7, 2025
@PaulDance
Copy link
Contributor

r?

@rustbot
Copy link
Collaborator

rustbot commented Oct 14, 2025

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.

@PaulDance
Copy link
Contributor

r? libs

Copy link
Member

@ChrisDenton ChrisDenton left a 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.

View changes since this review

}

// Only a single handle to the PE file the process is spawned from is expected at this point.
assert_eq!(handle_count, 1);
Copy link
Member

@ChrisDenton ChrisDenton Oct 21, 2025

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.

Copy link
Contributor

@PaulDance PaulDance Oct 21, 2025

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.

Copy link
Member

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

@michaelvanstraten michaelvanstraten Oct 21, 2025

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 :)

Copy link
Contributor

@PaulDance PaulDance Oct 21, 2025

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.

Copy link
Contributor Author

@michaelvanstraten michaelvanstraten Oct 21, 2025

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.

@rustbot

This comment has been minimized.

@rustbot
Copy link
Collaborator

rustbot commented Oct 21, 2025

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.

@ChrisDenton
Copy link
Member

Looks good to me, thanks!

@bors r+ rollup

@bors
Copy link
Collaborator

bors commented Oct 22, 2025

📌 Commit 24b0c27 has been approved by ChrisDenton

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Oct 22, 2025
Zalathar added a commit to Zalathar/rust that referenced this pull request Oct 22, 2025
…les, r=ChrisDenton

Add new inherit_handles flag to CommandExt trait

This PR adds a new flag to the [`CommandExt`](https://doc.rust-lang.org/stable/std/os/windows/process/trait.CommandExt.html) 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>
bors added a commit that referenced this pull request Oct 23, 2025
Rollup of 4 pull requests

Successful merges:

 - #115501 (Add new inherit_handles flag to CommandExt trait)
 - #146629 (std: reorganize the UNIX-internal `weak` module)
 - #147762 (feat(rustdoc): `--emit=depinfo` output to stdout via `-`)
 - #148001 (fix: Don't add diff symbol to unchanged lines)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit e4bf8b3 into rust-lang:master Oct 23, 2025
11 checks passed
@rustbot rustbot added this to the 1.92.0 milestone Oct 23, 2025
rust-timer added a commit that referenced this pull request Oct 23, 2025
Rollup merge of #115501 - michaelvanstraten:set_inherit_handles, r=ChrisDenton

Add new inherit_handles flag to CommandExt trait

This PR adds a new flag to the [`CommandExt`](https://doc.rust-lang.org/stable/std/os/windows/process/trait.CommandExt.html) 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>
@PaulDance
Copy link
Contributor

Thank you both!

flip1995 pushed a commit to flip1995/rust-clippy that referenced this pull request Oct 27, 2025
Rollup of 4 pull requests

Successful merges:

 - rust-lang/rust#115501 (Add new inherit_handles flag to CommandExt trait)
 - rust-lang/rust#146629 (std: reorganize the UNIX-internal `weak` module)
 - rust-lang/rust#147762 (feat(rustdoc): `--emit=depinfo` output to stdout via `-`)
 - rust-lang/rust#148001 (fix: Don't add diff symbol to unchanged lines)

r? `@ghost`
`@rustbot` modify labels: rollup
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

O-windows Operating system: Windows S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants