You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core子进程无法从父进程继承Socket句柄(Windows)

.NET 6下Impersonation启动子进程时Socket句柄无法继承的原因及解决方案

核心原因分析

问题根源在于.NET 6(及.NET Core系列)与.NET Framework 4.8在进程启动+模拟上下文下的句柄继承策略以及底层Win32 API调用逻辑上的差异:

  • 句柄继承默认行为变化:.NET Framework中Process.Start()在UseShellExecute=false时,默认自动继承父进程中标记为可继承的句柄;但.NET 6中Process类的底层实现更严格,跨用户模拟上下文下会限制句柄的跨安全上下文继承,除非显式配置权限和继承属性。
  • 模拟上下文的句柄权限限制:通过WindowsIdentity.RunImpersonated模拟另一个用户启动子进程时,父进程Socket句柄的安全描述符默认不允许目标用户(模拟用户)访问,导致子进程无法继承。
  • 底层API调用差异:.NET Framework的Process.Start()在模拟上下文下可能隐式使用CreateProcessAsUser,而.NET 6中Process类对模拟场景的适配逻辑不同,未自动处理跨用户的句柄权限传递。

解决方案步骤

1. 确保Socket句柄标记为可继承

创建Socket时必须显式设置句柄为可继承状态,两种实现方式:

  • 使用Socket原生API:
Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 设置句柄可继承
listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.HandleInformation, (int)HandleInheritability.Inheritable);
  • 调用Win32 API SetHandleInformation:
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetHandleInformation(IntPtr hObject, uint dwMask, uint dwFlags);

const uint HANDLE_FLAG_INHERIT = 0x00000001;

// 获取Socket句柄后设置继承标记
IntPtr socketHandle = listener.Handle;
SetHandleInformation(socketHandle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);

2. 显式配置ProcessStartInfo的句柄继承属性

创建ProcessStartInfo时,显式开启句柄继承:

var processStartInfo = new ProcessStartInfo(command, arguments)
{
    UseShellExecute = false,
    InheritHandles = true // 显式开启,避免默认行为差异
};
var process = new Process { StartInfo = processStartInfo };

3. 修改Socket句柄的安全描述符,允许模拟用户访问

跨用户模拟场景下,需给Socket句柄添加目标用户的访问权限:

[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool SetSecurityInfo(IntPtr handle, SE_OBJECT_TYPE objectType, uint securityInformation, IntPtr psidOwner, IntPtr psidGroup, IntPtr pDacl, IntPtr pSacl);

enum SE_OBJECT_TYPE
{
    SE_KERNEL_OBJECT = 6 // Socket句柄属于内核对象
}

const uint DACL_SECURITY_INFORMATION = 0x00000004;

// 获取模拟用户的SID并设置权限
using (WindowsIdentity identity = new WindowsIdentity(mToken))
{
    var security = new KernelObjectSecurity();
    security.AddAccessRule(new AccessRule<KernelObjectRights>(
        identity.User,
        KernelObjectRights.GenericRead | KernelObjectRights.GenericWrite,
        AccessControlType.Allow));
    // 将安全设置应用到Socket句柄
    security.Persist(listener.Handle, SE_OBJECT_TYPE.SE_KERNEL_OBJECT);
}

4. 改用CreateProcessAsUser直接启动子进程

模拟场景下,直接调用Win32 API CreateProcessAsUser比.NET Process类更可靠:

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool CreateProcessAsUser(
    IntPtr hToken,
    string lpApplicationName,
    string lpCommandLine,
    IntPtr lpProcessAttributes,
    IntPtr lpThreadAttributes,
    bool bInheritHandles,
    uint dwCreationFlags,
    IntPtr lpEnvironment,
    string lpCurrentDirectory,
    [In] ref STARTUPINFO lpStartupInfo,
    out PROCESS_INFORMATION lpProcessInformation);

[StructLayout(LayoutKind.Sequential)]
private struct STARTUPINFO
{
    public int cb;
    public string lpReserved;
    public string lpDesktop;
    public string lpTitle;
    public int dwX;
    public int dwY;
    public int dwXSize;
    public int dwYSize;
    public int dwXCountChars;
    public int dwYCountChars;
    public int dwFillAttribute;
    public int dwFlags;
    public short wShowWindow;
    public short cbReserved2;
    public IntPtr lpReserved2;
    public IntPtr hStdInput;
    public IntPtr hStdOutput;
    public IntPtr hStdError;
}

[StructLayout(LayoutKind.Sequential)]
private struct PROCESS_INFORMATION
{
    public IntPtr hProcess;
    public IntPtr hThread;
    public int dwProcessId;
    public int dwThreadId;
}

[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);

// 调用CreateProcessAsUser启动子进程
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
PROCESS_INFORMATION pi;

bool success = CreateProcessAsUser(
    mToken,
    command,
    arguments,
    IntPtr.Zero,
    IntPtr.Zero,
    true, // 继承句柄
    0,
    IntPtr.Zero,
    null,
    ref si,
    out pi);

if (!success)
{
    int error = Marshal.GetLastWin32Error();
    throw new Win32Exception(error);
}

// 关闭不必要的句柄
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

验证方法

使用Sysinternals Process Explorer再次检查子进程的句柄列表,确认Socket句柄是否存在;同时可通过GetHandleInformation验证Socket句柄的继承标记是否正确设置。

内容的提问来源于stack exchange,提问作者Michael

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 19:20:30