7

I have a service that is responsible for starting/monitoring an interactive process in user session once user logins and console session connects. Service is set to start automatically so its up and running before user login. All work fine on first login and i get the user process started/restarted correctly.

What happens is that if user signs out and re logins service is no longer able to start a user process correctly. CreateProcessAsUser returns no error but as soon as user process is started its exiting with -1073741502 (0xC0000142) exit code.

If i restart the service then its again able to launch user process without any errors.

I can post full source of how the service creates user process if required.

Edit

    try
            {
                //WE ALREADY HAVE A CLIENT ATTACHED , DONT START A NEW ONE
                if (ClientProcessId != null)
                    return;

                var ACTIVE_CONSOLE_SESSION = ListSessions()
                    .Where(SESSION => SESSION.State == WTS_CONNECTSTATE_CLASS.WTSActive)
                    .FirstOrDefault();

                if (ACTIVE_CONSOLE_SESSION == null)
                    return;

                CONSOLE_SESSION_ID = (uint)ACTIVE_CONSOLE_SESSION.Id;

                IntPtr USER_TOKEN = IntPtr.Zero;
                IntPtr ENVIRONMENT = IntPtr.Zero;
                IntPtr LINKED_TOKEN = IntPtr.Zero;

                try
                {
                    try
                    {
                        if (!Wtsapi32.WTSQueryUserToken(CONSOLE_SESSION_ID.Value, out USER_TOKEN))
                            throw new Win32Exception();
                    }
                    catch (Win32Exception wex)
                    {
                        EntryPoint.TryWriteToCacheLog($"{nameof(Wtsapi32.WTSQueryUserToken)} : console session id {CONSOLE_SESSION_ID} error {wex.ErrorCode} , native error {wex.NativeErrorCode}");
                        throw;
                    }

                    try
                    {
                        if (!Userenv.CreateEnvironmentBlock(out ENVIRONMENT, USER_TOKEN, true))
                            throw new Win32Exception();
                    }
                    catch (Win32Exception wex)
                    {
                        EntryPoint.TryWriteToCacheLog($"{nameof(Userenv.CreateEnvironmentBlock)} : error {wex.ErrorCode} , native error {wex.NativeErrorCode}");
                        throw;
                    }

                    try
                    {
                        LINKED_TOKEN = CoreProcess.GetLinkedTokeIfRequiered(USER_TOKEN);
                    }
                    catch (Win32Exception wex)
                    {
                        EntryPoint.TryWriteToCacheLog($"{nameof(CoreProcess.GetLinkedTokeIfRequiered)} : error {wex.ErrorCode} , native error {wex.NativeErrorCode}");
                        throw;
                    }

                    //GET PROCESS PATHS
                    string FILE_NAME = EntryPoint.PROCESS_FULL_FILE_NAME;
                    string WORKING_DIRECTORY = EntryPoint.PROCESS_DIRECTORY;

                    //GET CURRENT COMMAND LINE ARGUMENTS
                    var CMD_ARGS = Environment.GetCommandLineArgs();

                    //FIRST ARGUMENT WILL ALWAYS HAVE FULL PROCESS PATH,OTHER ARGUMENTS ARE OPTIONAL
                    var ARGUMENTS_STRING = CMD_ARGS
                        .Skip(1)
                        .DefaultIfEmpty()
                        .Aggregate((first, next) => ' ' + first + ' ' + next);

                    var ARGUMENTS = new StringBuilder(ARGUMENTS_STRING);

                    var START_INFO = new STARTUPINFO();
                    START_INFO.cb = Marshal.SizeOf(START_INFO);
                    START_INFO.lpDesktop = @"winsta0\default";

                    var PROCESS_INFO = new PROCESS_INFORMATION();
                    uint dwCreationFlags = NORMAL_PRIORITY_CLASS | (int)(PROCESS_CREATE_FLAG.CREATE_NEW_CONSOLE | PROCESS_CREATE_FLAG.CREATE_UNICODE_ENVIRONMENT);

                    try
                    {
                        if (!AdvApi32.CreateProcessAsUser(LINKED_TOKEN,
                            FILE_NAME,
                            ARGUMENTS,
                            IntPtr.Zero,
                            IntPtr.Zero,
                            true,
                            dwCreationFlags,
                            ENVIRONMENT,
                            WORKING_DIRECTORY,
                            ref START_INFO,
                            out PROCESS_INFO))
                            throw new Win32Exception();

                        if (PROCESS_INFO.hThread != IntPtr.Zero)
                        {
                            ClientProcessId = PROCESS_INFO.dwProcessId;
                            ClientSessionId = CONSOLE_SESSION_ID;

                            EntryPoint.TryWriteToCacheLog($"{nameof(AdvApi32.CreateProcessAsUser)} : Created porocess {ClientProcessId} in session {CONSOLE_SESSION_ID}.");
                        }
                    }
                    catch (Win32Exception wex)
                    {
                        EntryPoint.TryWriteToCacheLog($"{nameof(AdvApi32.CreateProcessAsUser)} : error {wex.ErrorCode} , native error {wex.NativeErrorCode}");
                        throw;
                    }
                }
                catch (Win32Exception wex)
                {
                    switch (wex.NativeErrorCode)
                    {
                        case 5:
                        case 1008:

                            tryCount++;
                            if (tryCount >= START_RETRIES)
                                throw;

                            Thread.Sleep(RETRY_WAIT_SPAN);

                            if (DisableCallBacks)
                                return;

                            CreateProcess(tryCount);

                            break;
                        default:
                            throw;
                    }
                }
                catch
                {
                    throw;
                }
                finally
                {
                    Userenv.DestroyEnvironmentBlock(ENVIRONMENT);
                    Kernel32.CloseHandle(USER_TOKEN);
                    if (USER_TOKEN != LINKED_TOKEN)
                        Kernel32.CloseHandle(LINKED_TOKEN);
                }
            }
            catch (Exception ex)
            {
                EntryPoint.TryWriteToCacheLog($"{nameof(CreateProcess)} failed after {tryCount} retries, console seesion id {(CONSOLE_SESSION_ID != null ? CONSOLE_SESSION_ID.ToString() : "Unobtained")}.", ex.ToString());
            }
            finally
            {
                Monitor.Exit(CREATE_LOCK);
            }

  public static TOKEN_ELEVATION_TYPE GetTokenElevationLevel(IntPtr hToken)
    {
        int TOKEN_INFO_LENGTH = Marshal.SizeOf(typeof(int));
        IntPtr TOKEN_INFO_POINTER = Marshal.AllocHGlobal(TOKEN_INFO_LENGTH);

        try
        {
            if (!AdvApi32.GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenElevationType, TOKEN_INFO_POINTER, TOKEN_INFO_LENGTH, out TOKEN_INFO_LENGTH))
                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());

            return (TOKEN_ELEVATION_TYPE)Marshal.ReadInt32(TOKEN_INFO_POINTER);
        }
        catch
        {
            throw;
        }
        finally
        {
            if (TOKEN_INFO_POINTER != IntPtr.Zero)
                Marshal.FreeHGlobal(TOKEN_INFO_POINTER);
        }
    }

 public static IntPtr GetLinkedTokeIfRequiered(IntPtr hToken)
    {
        var TOKEN_ELEVATION = GetTokenElevationLevel(hToken);

        if (TOKEN_ELEVATION != TOKEN_ELEVATION_TYPE.TokenElevationTypeLimited)
            return hToken;

        int TOKEN_INFO_LENGHT = Marshal.SizeOf(typeof(IntPtr));
        IntPtr LINKED_TOKEN_INFO = Marshal.AllocHGlobal(TOKEN_INFO_LENGHT);

        try
        {
            if (!AdvApi32.GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenLinkedToken, LINKED_TOKEN_INFO, TOKEN_INFO_LENGHT, out TOKEN_INFO_LENGHT))
                throw new Win32Exception();

            return Marshal.ReadIntPtr(LINKED_TOKEN_INFO);
        }
        finally
        {
            if (LINKED_TOKEN_INFO != IntPtr.Zero)
                Marshal.Release(LINKED_TOKEN_INFO);
        }
    }
NullReference
  • 862
  • 1
  • 11
  • 27
  • 2
    this mean that your service cache some data from user session . after session terminated - this data become incorrect. when you restart service - it use new data instead old cache – RbMm Jun 01 '18 at 13:26
  • hmm, that what i thought but i do release any handles that i obtain :( i will re-check! – NullReference Jun 01 '18 at 13:41
  • 1
    Not enough desktop heap left to load user32.dll. Fits the "things you should never do in a service" category. [This blog post](https://blogs.msdn.microsoft.com/winsdk/2015/06/03/what-is-up-with-the-application-failed-to-initialize-properly-0xc0000142-error/) tells you what went wrong and what to do about it. – Hans Passant Jun 01 '18 at 15:09
  • It's not likely to be associated with Desktop heap since the OP claims to be starting an interactive process in the user's session. This should be straight forward. Get the user's session number, call `WTSQueryUserToken`, and then `CreateProcessAsUser`. Likely user32.dll is failing to initialize, which, given it's not a Desktop heap problem, implies the token doesn't have the correct logon session SID, but I'm not certain how the OP ends up in this state. – Eryk Sun Jun 02 '18 at 06:49
  • @RbMm It doesnt seem that i am leaking any resource in between sessions , all hadles are released correctly :( eryksun indeed that is exactly what i am doing , as i said service is able to start restart user process multiple times as long as active console session does not change. I am at dead end here :( – NullReference Jun 04 '18 at 08:41
  • for say more - need some code example (src or binary) which can reproduce this effect, some more info – RbMm Jun 04 '18 at 09:11
  • added source, as i said the problem appears only once user session changes. The code is executed on console connect/user login. – NullReference Jun 04 '18 at 10:51
  • So this occurred on first start so it seems its not only linked to the session change.I am checking the console session used for launching and its always correct, tried to have some delays before trying still same effect and it just impossible to start process in this state. – NullReference Jun 05 '18 at 08:27
  • What does `CoreProcess.GetLinkedTokeIfRequiered(USER_TOKEN);` do? I translated your code into C++ (apart from that bit, which appears to be something of your own) and it works fine. Will claim bounty if it's this! Also, if you're launching a GUI process, you don't need `CREATE_NEW_CONSOLE ` (although it seems to be harmless). – Paul Sanders Jun 06 '18 at 18:46
  • @PaulSanders i have updated the source – NullReference Jun 07 '18 at 11:56

2 Answers2

2

Thanks you for posting the rest of the code. I see you are launching the process elevated. I added this to my test service and it still works correctly.

But I think the problem might be this line in GetLinkedTokeIfRequiered():

Marshal.Release(LINKED_TOKEN_INFO);

That should obviously be:

Marshal.FreeHGlobal(LINKED_TOKEN_INFO);

Fix that and it might just work. As things are, I'm amazed it's not crashing.

Not easy for me, digging through this. C# interop not my strong suit.


For the OP's benefit, complete source code of my test service, written in C++, which works:

#include <windows.h>
#include <wtsapi32.h>
#include <userenv.h>
#include <tchar.h>
#include <stdio.h>

#pragma comment (lib, "user32.lib")
#pragma comment (lib, "wtsapi32.lib")
#pragma comment (lib, "userenv.lib")
#pragma comment (lib, "advapi32.lib")

DWORD report_error (const char *operation)
{
    DWORD err = GetLastError ();
    return err;
}

// Launch notepad as currently logged-on user
DWORD LaunchProcess (DWORD SessionId, const char **failed_operation)
{
    HANDLE hToken;
    BOOL ok = WTSQueryUserToken (SessionId, &hToken);
    if (!ok)
        return report_error (*failed_operation = "WTSQueryUserToken");

    void *environment = NULL;
    ok = CreateEnvironmentBlock (&environment, hToken, TRUE);

    if (!ok)
    {
        CloseHandle (hToken);
        return report_error (*failed_operation = "CreateEnvironmentBlock");
    }

    TOKEN_LINKED_TOKEN lto;
    DWORD nbytes;
    ok = GetTokenInformation (hToken, TokenLinkedToken, &lto, sizeof (lto), &nbytes);

    if (ok)
    {
        CloseHandle (hToken);
        hToken = lto.LinkedToken;
    }

    STARTUPINFO si = { sizeof (si) } ;
    PROCESS_INFORMATION pi = { } ;
    si.lpDesktop = "winsta0\\default";

    // Do NOT want to inherit handles here, surely
    DWORD dwCreationFlags = NORMAL_PRIORITY_CLASS | /* CREATE_NEW_CONSOLE | */ CREATE_UNICODE_ENVIRONMENT;
    ok = CreateProcessAsUser (hToken, "c:\\windows\\system32\\notepad.exe", NULL, NULL, NULL, FALSE,
        dwCreationFlags, environment, NULL, &si, &pi);

    DestroyEnvironmentBlock (environment);
    CloseHandle (hToken);

    if (!ok)
        return report_error (*failed_operation = "CreateProcessAsUser");

    CloseHandle (pi.hThread);
    CloseHandle (pi.hProcess);
    return 0;
}

// Determine the session ID of the currently logged-on user
DWORD GetCurrentSessionId ()
{
    WTS_SESSION_INFO *pSessionInfo;
    DWORD n_sessions = 0;
    BOOL ok = WTSEnumerateSessions (WTS_CURRENT_SERVER, 0, 1, &pSessionInfo, &n_sessions);
    if (!ok)
        return 0;

    DWORD SessionId = 0;

    for (DWORD i = 0; i < n_sessions; ++i)
    {
        if (pSessionInfo [i].State == WTSActive)
        {
            SessionId = pSessionInfo [i].SessionId;
            break;
        }
    }

    WTSFreeMemory (pSessionInfo);
    return SessionId;
}


#define SERVICE_NAME __T ("demo_service")

bool quit;

// CtrlHandler callback
DWORD WINAPI CtrlHandler (DWORD dwControl, DWORD  dwEventType, LPVOID lpEventData, LPVOID lpContext)
{
    if (dwControl == SERVICE_CONTROL_STOP)
        quit = true;
    return NO_ERROR;
}

// SvcMain callback
VOID WINAPI SvcMain (DWORD dwArgc, LPTSTR *lpszArgv)
{
    // Register for callbacks
    SERVICE_STATUS_HANDLE sh = RegisterServiceCtrlHandlerEx (SERVICE_NAME, CtrlHandler, NULL);

    // Tell the SCM that we are up and running
    SERVICE_STATUS ss = { };
    ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    ss.dwCurrentState = SERVICE_RUNNING;
    ss.dwControlsAccepted = SERVICE_ACCEPT_STOP;
    SetServiceStatus (sh, &ss);

    TCHAR buf [256];
    const TCHAR *title = __T ("(c) 2018 Contoso Corporation");

    while (!quit)
    {
        DWORD response = IDOK;

        DWORD SessionId = GetCurrentSessionId ();
        if (SessionId == 0)
        {
            Sleep (2000);
            continue;
        }

        // Pop-up a message on the screen of the currently logged-on user (session 1)
        _stprintf (buf, __T ("Ready to launch..., SessionId = %d"), SessionId);
        WTSSendMessage (WTS_CURRENT_SERVER_HANDLE, SessionId, (TCHAR *) title, _tcslen (title),
            buf, _tcslen (buf), MB_OKCANCEL, 0, &response, TRUE);
        if (response == IDCANCEL)
            break;

        const char *failed_operation = "";
        DWORD dwResult = LaunchProcess (SessionId, &failed_operation);

        // Report results
        _stprintf (buf, __T ("LaunchProcess returned %lx from %s"), dwResult, failed_operation);
        WTSSendMessage (WTS_CURRENT_SERVER_HANDLE, SessionId, (char *) title, _tcslen (title),
            buf, _tcslen (buf), MB_OK, 0, &response, TRUE);

        FILE *logfile = fopen ("g:\\temp\\service.log", "at");
        if (logfile)
        {
            fprintf (logfile, "%s\n", buf);
            fclose (logfile);
        }
    }

    // Tell the SCM we are going away and exit
    ss.dwCurrentState = SERVICE_STOPPED;
    SetServiceStatus (sh, &ss);
}


// main
int main (void)
{
    SERVICE_TABLE_ENTRY DispatchTable [] = 
    { 
        { SERVICE_NAME, SvcMain }, 
        { NULL, NULL } 
    }; 

    // This call returns when the service has stopped. 
    // The process should simply terminate when the call returns.
    StartServiceCtrlDispatcher (DispatchTable);
    return 0;
}
Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • You are right about FreeHGlobal but still even with that the problem persist. As i said the problem appears 100% if user logs out and re logins and only some times on first start. – NullReference Jun 07 '18 at 16:05
  • OK, I'm out of ideas then, sorry. I posted my code, maybe it will help. – Paul Sanders Jun 07 '18 at 16:19
-1

Error is STATUS_DLL_INIT_FAILED which means a dynamically loaded DLL is missing. Maybe you specify the wrong working directory and some call to LoadLibrary("lib_with_no_path.dll") fails?

You should be able to see which DLL is missing if you look in the Event Viewer.

l33t
  • 18,692
  • 16
  • 103
  • 180