rust-lang / libs-team

The home of the library team
Apache License 2.0
115 stars 18 forks source link

Ability to stop child process from Inheriting Handles #264

Open michaelvanstraten opened 1 year ago

michaelvanstraten commented 1 year ago

API Change Proposal

Problem Statement

Currently, there is no mechanism in the Rust standard library to create a child process on Windows that does not inherit handles from the calling process.

Motivating Examples or Use Cases

Handle inheritance can be problematic in multi-threaded programs, as different command-spawning actions may require passing different files to the child process. In addition, improving security by preventing the child process from acquiring certain handles is essential.

Disabling handle inheritance when unnecessary is important for several reasons:

  1. Inheriting unnecessary handles is inefficient and prevents kernel objects from being cleaned up.
  2. In some cases, it might allow the child process to interfere with the inherited handles, although it would need to locate them first.

Solution Sketch

To address this issue, we propose adding a new flag to the CommandExt trait in Rust's standard library. This flag will determine whether the child process should inherit handles from the calling process.

/// If this flag is set to `true`, each inheritable handle in the calling process is inherited by the new process.
/// If the flag is `false`, the handles are not inherited.
///
/// The default value for this flag is `true`.
///
/// **Note** that inherited handles have the same value and access rights as the original handles. For additional discussion of inheritable handles, see [Remarks][1].
///
/// [1]: <https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw#remarks>
#[unstable(feature = "windows_process_extensions_inherit_handles", issue = "")]
fn inherit_handles(&mut self, inherit_handles: bool) -> &mut process::Command;

Additionally, the proposed change will affect the underlying CreateProcessW function, as shown below:

cvt(c::CreateProcessW(
    program.as_ptr(),
    cmd_str.as_mut_ptr(),
    ptr::null_mut(),
    ptr::null_mut(),
    inherit_handles,
    flags,
    envp,
    dirp,
    si_ptr,
    &mut pi,
))

Alternatives

Links and Related Work

For further reference, please consult the following resources:

ChrisDenton commented 10 months ago

This was discussed in the libs-api meeting. It was suggested that perhaps a better API would be to give an array of handles to inherit, with an empty array automatically setting inheritance to false. It was also mentioned that however this bool is set, it does effectively override Stdio. E.g. Stdio::inherit will be ignored.

Note: A motivating use case for this option is in the pseudo console example code: Creating the Hosted Process. The pseudo gets passed differently so inheritance is unnecessary. This works because if any of stdout, stdin or stderror are set to null then a child process will be given new handles derived from the console it's attached to (assuming it's not a gui process).

michaelvanstraten commented 10 months ago

Sorry for the late response, I recently had a lot on my hands with the new Semester at University and a new Job.

With this plan, it still eludes me how you could both pass some handles to a process while stopping it from inheriting other.

jmillikin commented 9 months ago

With this plan, it still eludes me how you could both pass some handles to a process while stopping it from inheriting other.

Windows versions >= Vista support specifying a list of handles to inherit when creating a process by setting the process attribute PROC_THREAD_ATTRIBUTE_HANDLE_LIST in the extended startup info passed to CreateProcessW().

The setup code is ... not straightforward. Raymond Chen has a brief writeup on it at https://devblogs.microsoft.com/oldnewthing/20111216-00/?p=8873, with sample code.