0

I've been trying to get this call to cooperate, but to no success.

I'm trying to get the SID value for the current user in order to get the User's account rights (using LsaEnumerateAccountRights). Although I'm lost as to why My call to GetTokenInformation is returning false. No error in retrieving the process token.

Here is my work so far on the subject:

    HANDLE h_Process;
HANDLE h_Token;
HANDLE h_retToken;

TOKEN_USER tp;
DWORD cb = sizeof(TOKEN_USER);
PDWORD ret;

DWORD dw_TokenLength;

h_Process = GetCurrentProcess();

if (OpenProcessToken(h_Process, TOKEN_READ, &h_Token) == FALSE)
{
    printf("Error: Couldn't open the process token\n");
    return -1;
}

if (GetTokenInformation(h_Token, TokenUser, &tp, cb, &dw_TokenLength) == FALSE)
{
    printf("Error: Could not retrieve Token User information");
    return -1;
}

And along with it, I might as well ask a follow up question that I have not yet encountered, how to retrieve the SID from the formed TOKEN_USER structure?

I apologize ahead of time for such a simple question, I'm just stumped and would like some help to continue. All the questions related to this one are far more complicated and give little insight to my current problem.

Thanks in advance, Jon

Jon
  • 81
  • 2
  • 7

2 Answers2

1

According to the documentation For GetTokenInformation, if the function fails you can retrieve more information via a call to GetLastError.

Return Value

If the function succeeds, the return value is nonzero.

If the function fails, the return value is zero. To get extended error information, call GetLastError.

So you need to implement some checking for the extended error:

if (!GetTokenInformation(h_Token, TokenUser, &tp, cb, &dw_TokenLength))
{
    int lastError = GetLastError();

    // Should be a switch, of course. Omitted for brevity
    if (lastError == ERROR_INSUFFICIENT_BUFFER) 
    {
        //
    }
}  

As a general rule of thumb when using WinAPI functions that have varying buffer requirements, you typically

  • Call the function with a NULL buffer to determine the buffer size needed (in this case, returned in the ReturnLength parameter)
  • Allocate a buffer of the indicated size
  • Call the function again, passing the allocated buffer, to obtain the information
Community
  • 1
  • 1
Ken White
  • 123,280
  • 14
  • 225
  • 444
  • Well, good call on the insufficient buffer. Why does that error occur? I ran into this and noticed that there is an apparent need for two calls to the method ([link](http://stackoverflow.com/questions/3670984/gettokeninformation-first-call-what-for)), I don't understand the need for two calls. Also why does DWORD cb = sizeof(TOKEN_USER) not provide sufficient memory allocation / buffer? – Jon Jan 24 '14 at 01:10
  • 1
    The error occurs because the buffer is insufficient. :-) It's not large enough for the content. Once again, I refer you to the documentation. If the function fails, the `ReturnLength` parameter is set to the size of the buffer needed, so you can allocate sufficient memory and call the function again. The general rule for many API calls that have variable buffer requirements is "Call the function once with a NULL buffer to determine the buffer size needed, allocate the memory, and then call it again to actually retrieve the information". – Ken White Jan 24 '14 at 01:13
  • I'll try to answer your actual question. The reason Windows often does this "call first to get the size" thing is struct version changes. When you build, the compiler knows what was in that struct was when it (compiler) was released. When you run the app on an OS built before or after that compiler, the struct might have been changed by MS and have more or less fields. This can only be known at runtime, so that's why they'll return that code and let you try again. You won't know what the extra bytes are, so you will generally ignore them. – user2957811 Apr 04 '17 at 01:41
  • 1
    Or in this case it's not that at all! Turns out the actual SID bytes are written into this buffer too (which explains the size variability) and the PSID in SID_AND_ATTRIBUTES just points to this area. In my case PSID pointed to the returned buffer +0x10 which is sizeof(TOKEN_USER) and suggests that the struct did not change size at all but that this function appended the SID data to the returned buffer. – user2957811 Apr 04 '17 at 02:24
  • @user2957811: You *are* aware that I'm not the one who asked the question, and that nothing you say is going to be seen by the person who did ask? – Ken White Apr 04 '17 at 02:28
1

The first thing to understand is that Win32 UM (user mode) APIs that result into system calls generally require that you provide the buffer up front. This has to do with the fact that the kernel can access UM heap allocations, and UM cannot access KM allocations.

These calls typically follow a convention where you call once to get the required buffer size, and then call again with an allocated buffer that is large enough. It is even better though if you can create a reasonably sized buffer upfront. System calls can be expensive because of the context switching that it causes, so going from 2 calls to 1 can be a big performance improvement if it is a hot path.

Here is a sample of what you need. This has a loop that will try forever, but it is also common to just try twice. If the needed buffer is <= 128, it will only call once.

DWORD bytesReturned = 128;
LPVOID tokenUser = nullptr;
auto cleanup = ScopeExit([&]() 
{
    LocalFree(tokenUser);
});
for (;;) {
    tokenUser = LocalAlloc(LMEM_FIXED, bytesReturned);
    THROW_HR_IF_NULL(E_OUTOFMEMORY, tokenUser);
    if (!GetTokenInformation(token.get(), TokenUser, &tokenUser, bytesReturned, &bytesReturned))
    {
        if (ERROR_INSUFFICIENT_BUFFER == GetLastError())
        {
            LocalFree(tokenUser);
            tokenUser = nullptr;
            continue;
        }
        THROW_HR(HRESULT_FROM_WIN32(GetLastError()));
    }
    break;
}

The other big problem with your code is that you are passing in a reference to TOKEN_USER tp. The API actually just takes a PVOID. The SID's buffer will just be in tokenUser's buffer. You will need to cast it to a TOKEN_USER* to correctly access the memory.

sam msft
  • 537
  • 6
  • 17