Search code examples
javac++windowswinapijna

CreateProcessAsUserW error code 6 Invalid Handle JNA


I'm using the JNA to call the Windows API. I want to start a process (doesn't matter which) as a specific user. The two API calls I use are:

LogonUserW succeeds, but CreateProcessAsUserW fails with Error 6. According to the Windows System Error Codes Doc, this corresponds to "ERROR_INVALID_HANDLE".

As far as I can tell, the only handle I pass in is the user handle. I don't see what could be wrong with that. According to the LogonUserW doc,

In most cases, the returned handle is a primary token that you can use in calls to the CreateProcessAsUser function. However, if you specify the LOGON32_LOGON_NETWORK flag, LogonUser returns an impersonation token that you cannot use in CreateProcessAsUser unless you call DuplicateTokenEx to convert it to a primary token.

However, I don't use LOGON32_LOGON_NETWORK.

Some of the struct parameters have handles, but I either pass NULL or they are populated by the API call instead of by me.

Here's the meat of my code:

final PointerByReference userPrimaryToken =
    new PointerByReference();
System.out.printf(
    "ptr.peer = %d\n",
    Pointer.nativeValue(userPrimaryToken.getValue())
);

final boolean logonOk = MyWinBase.INSTANCE.LogonUserW(
    toCString(<my-username>), // hidden
    toCString("ANT"),
    toCString(<my-password>), // hidden
    /* This logon type is intended for batch servers, where
    processes may be executing on behalf of a user without their
    direct intervention. This type is also for higher
    performance servers that process many plaintext
    authentication attempts at a time, such as mail or web
    servers.*/
    WinBase.LOGON32_LOGON_BATCH,
    WinBase.LOGON32_PROVIDER_DEFAULT,
    userPrimaryToken
);
System.out.printf("ok = %b\n", logonOk);
System.out.printf(
    "ptr.peer = %d\n",
    Pointer.nativeValue(userPrimaryToken.getValue())
);

final STARTUPINFOW.ByReference startupInfoW =
    new STARTUPINFOW.ByReference();
startupInfoW.cb = startupInfoW.size();
startupInfoW.lpReserved = Pointer.NULL;
startupInfoW.lpDesktop = Pointer.NULL;
startupInfoW.lpTitle = Pointer.NULL;
startupInfoW.dwFlags
    = startupInfoW.dwX = startupInfoW.dwY
    = startupInfoW.dwXSize = startupInfoW.dwYSize
    = startupInfoW.dwXCountChars = startupInfoW.dwYCountChars
    = startupInfoW.dwFillAttribute
    = startupInfoW.wShowWindow
    = 0;
startupInfoW.cbReserved2  = 0;
startupInfoW.lpReserved2 = Pointer.NULL;
startupInfoW.hStdInput = startupInfoW.hStdOutput
    = startupInfoW.hStdError
    = Pointer.NULL;

final PROCESS_INFORMATION.ByReference processInformation =
    new PROCESS_INFORMATION.ByReference();
processInformation.hProcess = processInformation.hThread
    = Pointer.NULL;
processInformation.dwProcessId = processInformation.dwThreadId
    = 0;

final boolean createProcessOk = MyProcessThreadsApi.INSTANCE
    .CreateProcessAsUserW(
        userPrimaryToken.getPointer(),
        toCString("C:\\Windows\\System32\\cmd.exe"),
        // execute and terminate
        toCString("/c whoami > whoami.txt"),
        Pointer.NULL,
        Pointer.NULL,
        false,
        WinBase.CREATE_UNICODE_ENVIRONMENT,
        new PointerByReference(),
        Pointer.NULL,
        startupInfoW,
        processInformation
    );
System.out.printf("ok = %b\n", createProcessOk);
System.out.printf(
    "dwProcessId = %d\n", processInformation.dwProcessId
);
System.out.printf(
    "last err code = %d\n",
    ErrHandlingApi.INSTANCE.GetLastError()
);

Here's my output:

ptr.peer = 0
ok = true
ptr.peer = 1040
ok = false
dwProcessId = 0
last err code = 6

Any suggestions?


Solution

  • Looking at this piece of code:

    final PointerByReference userPrimaryToken = ...;
    

    Online documentation says it represents a pointer to pointer, C notation void**
    https://java-native-access.github.io/jna/4.2.1/com/sun/jna/ptr/PointerByReference.html
    On documentation for LogonUser it expects a PHALDLE pointer to a HANDLE, which is similar to a pointer to pointer, since HANDLE is similar to pointer (it is declared as typedef void *HANDLE;).
    https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonuserw

    BOOL LogonUserW(
      ....
      DWORD   dwLogonProvider,
      PHANDLE phToken
    );
    

    But in documentation to CreateProcessAsUser is specified that this function accepts a HANDLE, not a PHANDLE
    https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasuserw

    BOOL CreateProcessAsUserW(
      HANDLE                hToken,
      LPCWSTR               lpApplicationName,
      ....
    );
    

    So I would expect you to pass rather getValue than getPointer. By using getPointer you get the pointer itself, which is most probably the pointer to pointer in your case. I don't know JNA, but expectances are from knowledge of WinAPI

    final boolean createProcessOk = MyProcessThreadsApi.INSTANCE
        .CreateProcessAsUserW(
            userPrimaryToken.getValue(),
            ....
        );