Search code examples
c#.net.net-coreunmanagedhandle

.Net core child processes do not inherit (socket) handles from parent process (Windows)


We implemented a Windows assembly in C#. This program is listening on a socket for incoming connections and starting child processes (unmanaged c code) which should then be using this socket. The child processes are started by a command line which includes the socket's handle as an argument. The entire mechanism works fine with .Net 4.8 Framework, but is failing with .Net 6. This is an excerpt of the code we are using:

        GetUserNameAndDomain(user, out var userName, out var domain);
        var process = new Process{StartInfo = new ProcessStartInfo(command, arguments){UseShellExecute = false}};
        WrapperImpersonationContext wrapperImpersonationContext  = new WrapperImpersonationContext(domain, userName, password.ToUnsecureString(), false, false);
        using (wrapperImpersonationContext) {
            Action impersonationAction = () =>
            {
                    process.Start();
            };
            wrapperImpersonationContext.Enter(impersonationAction);
        }

where the impersonation stuff is mainly doing something like

     public WrapperImpersonationContext(string domain, string username, string password, bool automaticallyEnter = true, bool logonAsService = false) {
        mDomain = domain;
        mUsername = username;
        mPassword = password;
        mLogonType = logonAsService ? LOGON32_LOGON_SERVICE : LOGON32_LOGON_INTERACTIVE;
        if (automaticallyEnter)
            Enter();
    }

    [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
    public void Enter(Action impersonationAction = null) {
        //if (IsInContext)
        //    return;
        mToken = new IntPtr(0);
        try {
            LogFactory.WriteVerbose("Logon to user '{0}' in domain '{1}'", mUsername, mDomain);
            mToken = IntPtr.Zero;
            bool logonSuccessfull = LogonUser(mUsername, mDomain, mPassword, mLogonType, LOGON32_PROVIDER_DEFAULT, ref mToken);
            if (logonSuccessfull == false) {
                int error = Marshal.GetLastWin32Error();
                throw new Win32Exception(error);
            }
            WindowsIdentity identity = new WindowsIdentity(mToken);
            WindowsIdentity.RunImpersonated(new Microsoft.Win32.SafeHandles.SafeAccessTokenHandle(mToken), impersonationAction);
            LogFactory.WriteVerbose("Impersonation was successfully");
        } catch (Exception exception) {
           LogFactory.WriteWarning(exception);
            throw;
        }
    }

We used Sysinternals Process Explorer to look at the parent and child processes' handles. Using .Net 4.8 we clearly saw that the socket handle passed on in the command line is available in the child processes' handle list. Using .Net 6 the passed on handle however is not. With .Net 6 less handles seem to be inherited compared to .Net 4.8.

How comes that .Net 6 is behaving differently in this area? All researches/reading that we did indicated that the 'handle inheritance' should still be the same in .Net 6.


Solution

  • We used GetHandleInformation() to find out that the socket we want to access in the child process was not marked as 'inheritable' in .NET 6 (whereas it is when using .Net 4.8). Now we use SetHandleInformation() to set the flag to 1 and voila - we can pass on the handle now.