KillOnParentExit rejects the Microsoft Store PowerShell execution alias with access denied

Đang mở
#137,325 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
48/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
csharp
Lĩnh vực
backend, operating-systems

Hướng nghiên cứu

Start with SafeProcessHandle.StartCore in src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs, the Windows startup path linked in the report. Reproduce the failure with the supplied program on Windows using the Store PowerShell alias, then compare it with the physical executable and the existing bounded replacement-job retry. Done when the alias starts with KillOnParentExit enabled, or the API reports and documents the unsupported case clearly.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

area-System.Diagnostics.Process untriaged

Description

ProcessStartInfo.KillOnParentExit=true causes Process.Start to reject the Microsoft Store PowerShell execution alias with native error 5 on .NET 11 GA. The same alias starts successfully when the option is false, including suspended creation. Applications that enable parent-exit cleanup therefore cannot launch this otherwise working executable.

Reproduction Steps

Install Microsoft Store PowerShell (tested package Microsoft.PowerShell 7.6.9.0 x64) with its execution alias enabled. Create a console project targeting net11.0 with implicit usings and nullable enabled, and replace Program.cs with:

using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

if (!OperatingSystem.IsWindows()) return;
Console.WriteLine($"Runtime {Environment.Version}; {RuntimeInformation.OSDescription}; {RuntimeInformation.ProcessArchitecture}");
var executable = args.Length > 0 ? Path.GetFullPath(args[0]) : Path.Combine(
    Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
    "Microsoft", "WindowsApps", "Microsoft.PowerShell_8wekyb3d8bbwe", "pwsh.exe");
using var launcher = Process.GetCurrentProcess();
using var outer = JoinJob(launcher);
using var inner = JoinJob(launcher);
foreach (var suspended in new[] { false, true })
foreach (var lifetime in new[] { false, true, false })
{
    using var child = new Process { StartInfo = new ProcessStartInfo(executable)
    {
        UseShellExecute = false,
        RedirectStandardInput = true,
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        InheritedHandles = [],
        CreateNoWindow = true,
        CreateNewProcessGroup = true,
        KillOnParentExit = lifetime,
        StartSuspended = suspended,
        ArgumentList = { "--version" },
    }};
    try
    {
        child.Start();
        if (suspended) child.SafeHandle.Resume();
        child.StandardInput.Close();
        var stdout = child.StandardOutput.ReadToEndAsync();
        var stderr = child.StandardError.ReadToEndAsync();
        await Task.WhenAll(child.WaitForExitAsync(), stdout, stderr).WaitAsync(TimeSpan.FromSeconds(10));
        Console.WriteLine($"KillOnParentExit={lifetime}; StartSuspended={suspended}; Exit={child.ExitCode}; Output={(await stdout).Trim()}; Error={(await stderr).Trim()}");
    }
    catch (Win32Exception exception)
    {
        Console.WriteLine($"KillOnParentExit={lifetime}; StartSuspended={suspended}; NativeErrorCode={exception.NativeErrorCode}; {exception.Message}");
    }
    finally
    {
        try
        {
            if (!child.HasExited) child.Kill();
            await child.WaitForExitAsync().WaitAsync(TimeSpan.FromSeconds(5));
        }
        catch (InvalidOperationException) { } // Start failed; there is no owned child.
    }
}

static SafeFileHandle JoinJob(Process launcher)
{
    // Plain, non-killing jobs; no resource limits, UI limits or breakaway changes.
    var job = Native.CreateJobObjectW(0, null);
    if (job.IsInvalid || !Native.AssignProcessToJobObject(job, launcher.SafeHandle))
    {
        var error = Marshal.GetLastPInvokeError();
        job.Dispose();
        throw new Win32Exception(error);
    }
    return job;
}

static class Native
{
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
    internal static extern SafeFileHandle CreateJobObjectW(nint attributes, string? name);
    [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern bool AssignProcessToJobObject(SafeFileHandle job, SafeProcessHandle process);
}

Run dotnet run with SDK 11.0.100. The default executable is the package-specific alias beneath the current user's local application data. An explicit executable path can instead be passed after --.

The program joins two ordinary, non-killing jobs before launching children. It neither changes pre-existing jobs nor requests breakaway. Every successfully created child is waited and cleaned up through its own process handle.

Expected behavior

The alias should start with parent-exit cleanup enabled, or the API should explicitly document and identify this unsupported executable type.

Actual behavior

Both StartSuspended=false and StartSuspended=true produce this false/true/false sequence:

KillOnParentExit=False: Exit=0; Output=PowerShell 7.6.9
KillOnParentExit=True: Win32Exception; NativeErrorCode=5; Access is denied.
KillOnParentExit=False: Exit=0; Output=PowerShell 7.6.9

The exception comes from SafeProcessHandle.StartCore during Process.Start. The runtime's bounded replacement-job retry does not make the alias start.

With the same redirected streams, restricted inheritance, process group, and nested jobs, the physical C:\Program Files\PowerShell\7\pwsh.exe (also PowerShell 7.6.9) succeeds for every combination. dotnet.exe --version and cmd.exe /d /c echo probe-ok also succeed with and without the option. This report claims the tested alias failure; it does not infer a general Windows job limitation.

Regression?

Not established. This is distinct from #134990: ordinary executable launches after containment changes now work in the GA runtime.

Known Workarounds

Launching the physical PowerShell executable succeeds. Disabling KillOnParentExit lets the alias start but removes the requested lifetime guarantee.

Configuration

  • SDK: 11.0.100.
  • Runtime: 11.0.0, commit 297acf930f.
  • OS: Microsoft Windows 10.0.26300, x64.
  • Microsoft Store package: Microsoft.PowerShell 7.6.9.0, x64.
  • PowerShell output: PowerShell 7.6.9.

Other information

Checked open and closed runtime issues for KillOnParentExit with alias, package, WindowsApps, and PowerShell terms. The Windows startup implementation on v11.0.0 and main at 719ca2546bd31a54244731185c55c8fc208204bf still supplies the lifetime job through the extended startup attributes. No alias-specific fix was found in main or the .NET 11 release notes.

Discovered while investigating ModularPipelines #6302. Please support this alias launch with KillOnParentExit, or document its restriction with an actionable error.

Ngôn ngữ chính
C#
Star
18.4k
Fork
5.8k
Merge trung bình
2 ngày 19 giờ
Pull request đã merge (30 ngày)
590

Chuẩn bị môi trường

Mở trong Codespaces

Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của dotnet/runtime

Tất cả issue của dotnet/runtime

Issue tương tự

Thêm issue về C#

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.