Skip to content

Add new process management APIs to SafeProcessHandle#124375

Open
Copilot wants to merge 36 commits intomainfrom
copilot/add-new-apis-to-safeprocesshandle
Open

Add new process management APIs to SafeProcessHandle#124375
Copilot wants to merge 36 commits intomainfrom
copilot/add-new-apis-to-safeprocesshandle

Conversation

Copy link
Contributor

Copilot AI commented Feb 13, 2026

Description

Implements the approved API surface for SafeProcessHandle (#123380): process lifecycle management via Start, StartSuspended, Open, Kill, KillProcessGroup, Resume, Signal, SignalProcessGroup, and various WaitForExit overloads. Adds ProcessExitStatus value type. Windows implementation is complete; Unix stubs throw NotImplementedException (separate PR).

New types

  • ProcessExitStatus — readonly struct with ExitCode, Signal, Canceled; implements IEquatable<T>

SafeProcessHandle new public API

  • ProcessId property (shared across platforms in SafeProcessHandle.cs)
  • Open(int processId) — opens existing process handle
  • Start / StartSuspended — process creation with explicit stdio handles
  • Kill / KillProcessGroup — termination with job object support
  • Resume — resumes processes created via StartSuspended (not a general-purpose NtResumeProcess); protected against double-resume via Interlocked.Exchange
  • Signal / SignalProcessGroup — sends PosixSignal (Windows: CTRL_C/CTRL_BREAK via GenerateConsoleCtrlEvent)
  • WaitForExit, TryWaitForExit, WaitForExitOrKillOnTimeout — synchronous wait variants
  • WaitForExitAsync, WaitForExitOrKillOnCancellationAsync — async wait with cancellation support

Windows implementation details

  • Uses STARTUPINFOEX + PROC_THREAD_ATTRIBUTE_HANDLE_LIST for explicit handle inheritance
  • Job objects for KillOnParentExit and CreateNewProcessGroup (process group termination)
  • Thread handle retention for StartSuspended/Resume; atomically cleared on resume to prevent double-resume
  • ValueStringBuilder instances are properly disposed after CreateProcess invocation
  • Uses default Win32Exception() constructor (auto-captures last error) everywhere except CreateProcess error handling
  • OOM-safe: handle cleanup in finally block prevents leaks if new SafeProcessHandle(...) throws
  • Error check immediately after CreateProcess call
  • Uses NativeMemory.Alloc (with two-arg version for integer overflow protection) instead of Marshal.AllocHGlobal; results stored in typed pointers (void*, IntPtr*) instead of IntPtr
  • Uses sizeof instead of Marshal.SizeOf for blittable interop structs
  • GetTimeoutInMilliseconds validates timeout range consistent with Process.ToTimeoutMilliseconds
  • WaitForExitOrKillOnTimeoutCore waits for the process to fully terminate after kill (TerminateProcess is asynchronous on Windows)
  • Does not close the pseudo-handle returned by GetCurrentProcess()

Architecture — ProcessUtils as shared dependency

  • ProcessUtils.cs holds s_processStartLock (ReaderWriterLockSlim) to avoid dependency cycles between Process and SafeProcessHandle
  • ProcessUtils.Windows.cs holds BuildArgs (accepts string resolvedFilePath, IList<string>? arguments to avoid loading ProcessStartOptions from Process callers) and GetEnvironmentVariablesBlock
  • Process.Windows.cs acquires write lock; SafeProcessHandle.Windows.cs acquires read lock — prevents accidental handle inheritance
  • ProcessStartOptions exposes HasArgumentsBeenAccessed and HasEnvironmentBeenAccessed to avoid unnecessary allocations

New/extended interop definitions

  • Interop.JobObjects.csCreateJobObjectW, SetInformationJobObject, TerminateJobObject
  • Interop.ProcThreadAttributeList.csSTARTUPINFOEX, attribute list APIs, CreateProcess overload
  • Interop.ConsoleCtrl.csSetConsoleCtrlHandler + GenerateConsoleCtrlEvent (consolidated)
  • Interop.HandleInformation.cs — extended with GetHandleInformation(IntPtr) and SetHandleInformation(IntPtr) overloads
  • Interop.ResumeThread_IntPtr.csResumeThread with IntPtr parameter

Tests

  • SafeProcessHandleTests.cs — cross-platform test class marked [PlatformSpecific(TestPlatforms.Windows)] with CreateTenSecondSleep() helper to reduce duplication; powershell-dependent tests use [ConditionalFact] with IsNotWindowsNanoNorServerCore; includes Start_WithNoArguments_Succeeds test (using hostname) to verify no-argument process invocation
  • SafeProcessHandleTests.Windows.cs — signal, process group, suspend/resume tests using Console.OpenStandardInputHandle() for stdin
ProcessStartOptions options = new("cmd.exe") { Arguments = { "/c", "echo hello" } };
using SafeProcessHandle handle = SafeProcessHandle.Start(options, input: null, output: null, error: null);
ProcessExitStatus status = handle.WaitForExit();
// status.ExitCode == 0, status.Canceled == false, status.Signal == null

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 22 commits February 11, 2026 13:33
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
…, Theory for utils

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
…h for sh

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
…syntax

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
… remove dead code

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
…names

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
@github-actions github-actions bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Feb 13, 2026
Copilot AI and others added 3 commits February 13, 2026 12:24
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
…s, and Windows implementation

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copilot AI changed the title [WIP] Add new APIs to SafeProcessHandle class Add new process management APIs to SafeProcessHandle Feb 13, 2026
…c, move lock/env to ProcessUtils, refactor BuildArgs

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 18, 2026

@copilot please address the feedback

All feedback addressed in 7973122.

Copy link
Member

@adamsitnik adamsitnik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot please address my feedback

…ocess without arguments

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Comment on lines 135 to 136
IntPtr heapHandlesPtr = (IntPtr)NativeMemory.Alloc((nuint)maxHandleCount, (nuint)sizeof(IntPtr));
IntPtr* handlesToInherit = (IntPtr*)heapHandlesPtr;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
IntPtr heapHandlesPtr = (IntPtr)NativeMemory.Alloc((nuint)maxHandleCount, (nuint)sizeof(IntPtr));
IntPtr* handlesToInherit = (IntPtr*)heapHandlesPtr;
IntPtr* handlesToInherit = (IntPtr*)NativeMemory.Alloc((nuint)maxHandleCount, (nuint)sizeof(IntPtr));

New code should use pointers for pointers instead of IntPtr

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in adeeb03. Now using IntPtr* handlesToInherit = (IntPtr*)NativeMemory.Alloc(...) directly and void* attributeListBuffer instead of IntPtr.

Interop.Kernel32.LPPROC_THREAD_ATTRIBUTE_LIST emptyList = default;
Interop.Kernel32.InitializeProcThreadAttributeList(emptyList, attributeCount, 0, ref size);

attributeListBuffer = (IntPtr)NativeMemory.Alloc((nuint)size);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in adeeb03. Changed attributeListBuffer from IntPtr to void* with NativeMemory.Alloc returning directly to it.

@adamsitnik
Copy link
Member

@copilot please address @jkotas feedback

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 18, 2026

@copilot please address @jkotas feedback

Done in adeeb03.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new SafeProcessHandle-based process management API surface (start/open/kill/signal/wait), with a Windows implementation plus supporting interop and tests, while introducing shared process-start serialization via ProcessUtils.

Changes:

  • Add new public SafeProcessHandle APIs (start/open/kill/signal/resume/wait) and ProcessId plumbing.
  • Implement the new APIs on Windows (CreateProcess + STARTUPINFOEX, handle allow-list, job objects, async wait).
  • Add tests and supporting shared helpers/interop (including consolidating console control interop).

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems Switch CoreLib to include consolidated Interop.ConsoleCtrl.cs.
src/libraries/System.Diagnostics.Process/tests/System.Diagnostics.Process.Tests.csproj Add new SafeProcessHandle tests and use consolidated console control interop.
src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.cs Add Windows-only tests for start/open/kill/wait APIs.
src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.Windows.cs Add Windows-specific tests for signaling, process groups, and suspend/resume.
src/libraries/System.Diagnostics.Process/tests/ProcessTests.Windows.cs Update to use common Interop.Kernel32.GenerateConsoleCtrlEvent.
src/libraries/System.Diagnostics.Process/tests/Interop.cs Remove local GenerateConsoleCtrlEvent P/Invoke now covered by common interop.
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessUtils.cs Introduce shared s_processStartLock for process start serialization.
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessUtils.Windows.cs Add helpers for argument building and environment block creation.
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessStartOptions.cs Add internal “Has*BeenAccessed” flags to avoid unnecessary allocations.
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs Replace create-process lock with ProcessUtils.s_processStartLock; move env-block helper use to ProcessUtils.
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs Use shared ProcessUtils.s_processStartLock instead of a per-file lock.
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.ConfigureTerminalForChildProcesses.Unix.cs Update terminal configuration assertions/locking to use the shared lock.
src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj Include new Windows interop files needed by SafeProcessHandle.
src/libraries/System.Diagnostics.Process/src/Resources/Strings.resx Add new resource strings for SafeProcessHandle error cases.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs Add new public API surface and shared helpers/validation.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs Implement Windows process lifecycle APIs (job objects, STARTUPINFOEX, async waits, signaling).
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.cs Add Unix stubs for the new APIs (currently NotImplementedException).
src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs Update reference contract for new SafeProcessHandle APIs and ProcessExitStatus.
src/libraries/Common/src/Interop/Windows/Kernel32/Interop.ResumeThread_IntPtr.cs Add ResumeThread(IntPtr) interop.
src/libraries/Common/src/Interop/Windows/Kernel32/Interop.ProcThreadAttributeList.cs Add STARTUPINFOEX / proc-thread attribute list interop and CreateProcess overload.
src/libraries/Common/src/Interop/Windows/Kernel32/Interop.JobObjects.cs Add job object interop needed for kill-on-parent-exit and process groups.
src/libraries/Common/src/Interop/Windows/Kernel32/Interop.HandleOptions.cs Add HANDLE_FLAG_INHERIT constant used for inherited handle preparation.
src/libraries/Common/src/Interop/Windows/Kernel32/Interop.HandleInformation.cs Add Get/SetHandleInformation(IntPtr, ...) overloads.
src/libraries/Common/src/Interop/Windows/Kernel32/Interop.ConsoleCtrl.cs Consolidate console control interop and add GenerateConsoleCtrlEvent.
src/libraries/Common/src/Interop/Windows/Advapi32/Interop.ProcessOptions.cs Add CREATE_SUSPENDED constant used for suspended process creation.

Comment on lines 218 to 224
public sealed partial class ProcessExitStatus
{
public ProcessExitStatus(int exitCode, bool canceled, System.Runtime.InteropServices.PosixSignal? signal = null) { throw null; }
public bool Canceled { get { throw null; } }
public int ExitCode { get { throw null; } }
public System.Runtime.InteropServices.PosixSignal? Signal { get { throw null; } }
}
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description states ProcessExitStatus is a readonly struct (value type) implementing IEquatable, but the public contract here exposes it as a sealed class. Please confirm the approved API shape and align either the implementation/ref contract or the PR description; this is a user-visible API surface difference.

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +63
private static SafeProcessHandle OpenCore(int processId) => throw new NotImplementedException();

private static SafeProcessHandle StartCore(ProcessStartOptions options, SafeFileHandle inputHandle, SafeFileHandle outputHandle, SafeFileHandle errorHandle, bool createSuspended) => throw new NotImplementedException();

private ProcessExitStatus WaitForExitCore() => throw new NotImplementedException();

private bool TryWaitForExitCore(int milliseconds, [NotNullWhen(true)] out ProcessExitStatus? exitStatus) => throw new NotImplementedException();

private ProcessExitStatus WaitForExitOrKillOnTimeoutCore(int milliseconds) => throw new NotImplementedException();

private Task<ProcessExitStatus> WaitForExitAsyncCore(CancellationToken cancellationToken) => throw new NotImplementedException();

private Task<ProcessExitStatus> WaitForExitOrKillOnCancellationAsyncCore(CancellationToken cancellationToken) => throw new NotImplementedException();

internal bool KillCore(bool throwOnError, bool entireProcessGroup = false) => throw new NotImplementedException();

private void ResumeCore() => throw new NotImplementedException();

private void SendSignalCore(PosixSignal signal, bool entireProcessGroup) => throw new NotImplementedException();
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unix implementations currently throw NotImplementedException for the new SafeProcessHandle APIs. For a public, cross-platform type, consider throwing PlatformNotSupportedException (or adding SupportedOSPlatform attributes) until Unix support is implemented, to avoid signaling an incomplete runtime implementation to callers in shipping builds.

Suggested change
private static SafeProcessHandle OpenCore(int processId) => throw new NotImplementedException();
private static SafeProcessHandle StartCore(ProcessStartOptions options, SafeFileHandle inputHandle, SafeFileHandle outputHandle, SafeFileHandle errorHandle, bool createSuspended) => throw new NotImplementedException();
private ProcessExitStatus WaitForExitCore() => throw new NotImplementedException();
private bool TryWaitForExitCore(int milliseconds, [NotNullWhen(true)] out ProcessExitStatus? exitStatus) => throw new NotImplementedException();
private ProcessExitStatus WaitForExitOrKillOnTimeoutCore(int milliseconds) => throw new NotImplementedException();
private Task<ProcessExitStatus> WaitForExitAsyncCore(CancellationToken cancellationToken) => throw new NotImplementedException();
private Task<ProcessExitStatus> WaitForExitOrKillOnCancellationAsyncCore(CancellationToken cancellationToken) => throw new NotImplementedException();
internal bool KillCore(bool throwOnError, bool entireProcessGroup = false) => throw new NotImplementedException();
private void ResumeCore() => throw new NotImplementedException();
private void SendSignalCore(PosixSignal signal, bool entireProcessGroup) => throw new NotImplementedException();
private static SafeProcessHandle OpenCore(int processId) => throw new PlatformNotSupportedException();
private static SafeProcessHandle StartCore(ProcessStartOptions options, SafeFileHandle inputHandle, SafeFileHandle outputHandle, SafeFileHandle errorHandle, bool createSuspended) => throw new PlatformNotSupportedException();
private ProcessExitStatus WaitForExitCore() => throw new PlatformNotSupportedException();
private bool TryWaitForExitCore(int milliseconds, [NotNullWhen(true)] out ProcessExitStatus? exitStatus) => throw new PlatformNotSupportedException();
private ProcessExitStatus WaitForExitOrKillOnTimeoutCore(int milliseconds) => throw new PlatformNotSupportedException();
private Task<ProcessExitStatus> WaitForExitAsyncCore(CancellationToken cancellationToken) => throw new PlatformNotSupportedException();
private Task<ProcessExitStatus> WaitForExitOrKillOnCancellationAsyncCore(CancellationToken cancellationToken) => throw new PlatformNotSupportedException();
internal bool KillCore(bool throwOnError, bool entireProcessGroup = false) => throw new PlatformNotSupportedException();
private void ResumeCore() => throw new PlatformNotSupportedException();
private void SendSignalCore(PosixSignal signal, bool entireProcessGroup) => throw new PlatformNotSupportedException();

Copilot uses AI. Check for mistakes.
Comment on lines 45 to 49
public SafeProcessHandle(IntPtr existingHandle, bool ownsHandle)
: base(ownsHandle)
{
SetHandle(existingHandle);
}
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ProcessId is a new public property, but the general constructors/initialization paths don’t set it (e.g., SafeProcessHandle(IntPtr existingHandle, ...) just calls SetHandle). As a result, SafeProcessHandle instances produced by existing Process APIs (and user-created wrappers around an existing handle) will report ProcessId==0 on Windows unless they come from the new Start/Open paths. Consider initializing ProcessId in the ctor (e.g., via Kernel32.GetProcessId) and/or providing an internal setter that Process can use after CreateProcess/ShellExecute paths.

Copilot uses AI. Check for mistakes.
{
var (handle, taskSource, wasCancelled) = ((SafeProcessHandle, TaskCompletionSource<bool>, StrongBox<bool>))state!;
wasCancelled.Value = handle.KillCore(throwOnError: false);
taskSource.TrySetResult(true);
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the cancellation callback, the TaskCompletionSource is completed immediately after KillCore() returns, but the process may still be running (TerminateProcess/TerminateJobObject are async). That can cause GetExitCode() to observe STILL_ACTIVE and throw, and it contradicts the method's doc that it “kills … and then waits for exit”. Consider waiting for processWaitHandle to signal after killing (or only completing the TCS from the wait-handle path) so exit status is retrieved after the process has actually terminated.

Suggested change
taskSource.TrySetResult(true);

Copilot uses AI. Check for mistakes.
Comment on lines +432 to +438
ctr = cancellationToken.Register(
static state =>
{
var taskSource = (TaskCompletionSource<bool>)state!;
taskSource.TrySetCanceled();
},
tcs);
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WaitForExitAsyncCore uses CancellationToken.Register + TaskCompletionSource.TrySetCanceled() without passing the cancellation token. Elsewhere in this repo (e.g., Process.WaitForExitAsync) the pattern is UnsafeRegister and TrySetCanceled(cancellationToken), which preserves the token on the thrown OperationCanceledException and avoids ExecutionContext capture overhead.

Suggested change
ctr = cancellationToken.Register(
static state =>
{
var taskSource = (TaskCompletionSource<bool>)state!;
taskSource.TrySetCanceled();
},
tcs);
ctr = cancellationToken.UnsafeRegister(
static state =>
{
var (taskSource, token) = ((TaskCompletionSource<bool> taskSource, CancellationToken token))state!;
taskSource.TrySetCanceled(token);
},
(tcs, cancellationToken));

Copilot uses AI. Check for mistakes.
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.SetConsoleCtrlHandler.cs">
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.ConsoleCtrl.cs">
<Link>Common\Interop\Windows\Interop.SetConsoleCtrlHandler.cs</Link>
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The metadata still points to the old SetConsoleCtrlHandler virtual path/name even though the included file is now Interop.ConsoleCtrl.cs under Kernel32. This can make IDE paths/PDB source paths misleading; consider updating the Link value to match the new file location/name (e.g., Common\Interop\Windows\Kernel32\Interop.ConsoleCtrl.cs).

Suggested change
<Link>Common\Interop\Windows\Interop.SetConsoleCtrlHandler.cs</Link>
<Link>Common\Interop\Windows\Kernel32\Interop.ConsoleCtrl.cs</Link>

Copilot uses AI. Check for mistakes.
if (!Interop.GenerateConsoleCtrlEvent(dwCtrlEvent, (uint)processId))
if (!Interop.Kernel32.GenerateConsoleCtrlEvent((int)dwCtrlEvent, processId))
{
int error = Marshal.GetLastWin32Error();
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interop.Kernel32.GenerateConsoleCtrlEvent is now a [LibraryImport] P/Invoke; prefer Marshal.GetLastPInvokeError() over Marshal.GetLastWin32Error() for consistency with other LibraryImport call sites in this PR and to ensure the correct last-error accessor is used.

Suggested change
int error = Marshal.GetLastWin32Error();
int error = Marshal.GetLastPInvokeError();

Copilot uses AI. Check for mistakes.

private void ResumeCore()
{
IntPtr threadHandle = Interlocked.Exchange(ref _threadHandle, IntPtr.Zero);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does ReleaseHandle need the same Interlocked.Exchange to avoid use-after-free if the user code happens to call this on a handle that is being disposed?

<value>Invalid process handle.</value>
</data>
<data name="KillProcessGroupWithoutNewProcessGroup" xml:space="preserve">
<value>Cannot terminate entire process group because the process was not started with CreateNewProcessGroup=true.</value>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is ProcessStartInfo.CreateNewProcessGroup as well. If you start the process with ProcessStartInfo.CreateNewProcessGroup=true, you are still going to get this message.

(This shows the problem with the overlap between ProcessStartInfo and ProcessStartInfo discussed offline.)

throw new Win32Exception(error);
}

// Transfer ownership: take the handle from the returned SafeProcessHandle and
Copy link
Member

@jkotas jkotas Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not following why we need to create a new SafeProcessHandle. Can we just set the process ID on the instance we have?

if (options.KillOnParentExit || options.CreateNewProcessGroup)
attributeCount++;

IntPtr size = IntPtr.Zero;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
IntPtr size = IntPtr.Zero;
nuint size = 0;

nuint would be better match (the argument type for InitializeProcThreadAttributeList is SIZE_T that is unsigned type

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(There are other signed vs. unsigned mismatches in the interop definitions added in this PR that would be nice to fix.)

Interop.Kernel32.LPPROC_THREAD_ATTRIBUTE_LIST emptyList = default;
Interop.Kernel32.InitializeProcThreadAttributeList(emptyList, attributeCount, 0, ref size);

attributeListBuffer = NativeMemory.Alloc((nuint)size);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
attributeListBuffer = NativeMemory.Alloc((nuint)size);
attributeListBuffer = NativeMemory.Alloc(size);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

Comments