6

Looking at MSDN documentaion for GetTokenInformation() and the Getting the Logon SID example, GetTokenInformation() needs to be called twice. The first call is to get the buffer size.

So, buffer size of what? Just say I use TokenUser as its second parameter, I see that the dwReturnLength returned by first call is not the size of TOKEN_USER structure.

Thanks in advance

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
Phantom
  • 271
  • 1
  • 3
  • 9

5 Answers5

8

The TOKEN_USER structure contains pointers (in particular, a pointer to a SID that itself has variable length). Those pointers have to point somewhere. The API function will expect a buffer big enough to hold not only the the TOKEN_USER structure, but also all the things that structure points to. The function tells you how much memory it needs for everything. It will all reside in adjacent memory.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
  • I count manually the SID string returned by second call, and it is equal to 43 characters, but the dwReturnLength return by first call is 36. So, where is 36 from? Just curious. :-) – Phantom Sep 08 '10 at 19:07
  • @Phantom: The SID is stored in binary, not in string form. The string form is just to make it human readable. – Billy ONeal Sep 08 '10 at 19:18
  • Oops, sorry, the 43 characters is returned by ConvertSidToStringSid(). So, 36 is the SID in binary from, isn't it? Now I understand. – Phantom Sep 08 '10 at 19:25
  • I tried eliminate the first call and give 100 for the last parameter of HeapAlloc() although I already know it only requires 36 in my case. After GetTokenInformation(), the actual buffer used set to 36 by GetTokenInformation() through the dwReturnLength parameter. So my conclusion is, it's safe to use 100 as a safe guest size, it will never larger than that. The good side is I don't have to call GetTokenInformation() twice. :-) – Phantom Sep 08 '10 at 19:49
  • 5
    What's wrong with calling it twice? That's the idiomatic way to do it. Nobody seeing your code will be confused if you call it twice, but people *will* be skeptical of your hard-coded buffer size. How do you know the length of every possible SID you could ever get? – Rob Kennedy Sep 08 '10 at 20:10
  • This is not about reader's confusion, but efficiency. Why we should call it twice if once is enough? I don't know every possible SID I possibly get, that's why I choose 100 allocation size, it's a big enough tollerance. I'm talking about Sid of Users. Looking at HKEY_USERS\{Sid}, the longest Sid is always the current user's Sid. Maybe some virus want to create Sid longer than that, but I assume it will never longer than 100 bytes in its binary form. If that really happens, the virus do a good job cheating my program. :-) – Phantom Sep 08 '10 at 21:11
  • What makes you think it has to be a *virus* that makes your program fail? It could also be any *legitimate* SID-issuing authority that issues a longer SID. How do you know you had a problem with efficiency, anyway? – Rob Kennedy Sep 08 '10 at 21:54
  • @Phantom Can you explain in simple terms, why are you so stubborn in accepting that it MUST be called twice? Looks like you're just hiding your real issue (if you ever had one). – Alex Sep 09 '10 at 01:08
  • Everyone: According to the the official Sid format documentation at MSDN: A SID in binary form is a [1 byte revision] + [1 byte SubauthorityCount] + [6 bytes of IdentifierAuthority] and plus a a variable number of 4 byte subauthorities, maxing out at 15. Thus, we've got [8 + 60 = 68] bytes total. – Phantom Sep 09 '10 at 07:50
  • So, theoritically, there is no Sid longer than 68 bytes. – Phantom Sep 09 '10 at 07:54
  • 68 Max length of SID in String format is 184 characters, e.g. S-1-281474976710655-4294967295-4294967295-4294967295-4294967295-4294967295- 4294967295-4294967295-4294967295-4294967295-4294967295-4294967295 -4294967295-4294967295-4294967295-4294967295 – Phantom Sep 09 '10 at 08:15
  • I quote it from here – Phantom Sep 09 '10 at 08:17
  • So, I want to rectify my previous statement, now it is "70 bytes allocation is absolutely safe." @Alexander, do you still think I'm stabborn here? :-; – Phantom Sep 09 '10 at 08:29
  • 1
    Yes, you're being stubborn. At least now you have some *evidence* for choosing a fixed-size buffer, as long as you're checking the `TokenUser` information class. Well done. You still don't have the right size, though, since you have to account for the full `TOKEN_USER` struct, not just one of the things it points to. Make sure you account for varying sizes of pointers on different platforms. Now, what about *the other 27 classes*? – Rob Kennedy Sep 09 '10 at 12:49
  • 1
    And what if Microsoft changes these limits in a later version of Windows? – Gerry Coll Sep 09 '10 at 13:05
  • @Gerry, looking at 184 characters of Sid, I don't think they will change the limit at least for 40 years ahead, and I may be not in the earth again at the time, and Microsoft maybe don't exist anymore :-). However @Rob, you show me the frightening downside, that'ts why I still comment until someone disproof it. OK, I will follow the MSDN style, but @Rob, AFAIK, rebuttal with proposition is NOT definition of "stubborn" in English, correct me if I'm wrong. – Phantom Sep 09 '10 at 14:12
  • @Phantom The API contract exists for a REASON. I feel that you urgently need for Raymond Chen's courses ;) http://blogs.msdn.com/b/oldnewthing/archive/2005/10/26/485133.aspx http://blogs.msdn.com/b/oldnewthing/archive/2006/01/09/510781.aspx You can't foresee the future. Ever. – Alex Sep 09 '10 at 19:41
4

Imbedded in the structure is a SID which is variable length, so the buffer size will depend on the size of the SID that will be included in the result.

Chris Taylor
  • 52,623
  • 10
  • 78
  • 89
4

The full example from your second URL should make it clear that how the length returned from the first call is used. You use this to allocate raw memory of that size - here this is the variable ptg - and cast it to PTOKEN_GROUPS for use in the second call.

// Get required buffer size and allocate the TOKEN_GROUPS buffer.

   if (!GetTokenInformation(
         hToken,         // handle to the access token
         TokenGroups,    // get information about the token's groups 
         (LPVOID) ptg,   // pointer to TOKEN_GROUPS buffer
         0,              // size of buffer
         &dwLength       // receives required buffer size
      )) 
   {
      if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) 
         goto Cleanup;

      ptg = (PTOKEN_GROUPS)HeapAlloc(GetProcessHeap(),
         HEAP_ZERO_MEMORY, dwLength);

      if (ptg == NULL)
         goto Cleanup;
   }

// Get the token group information from the access token.

   if (!GetTokenInformation(
         hToken,         // handle to the access token
         TokenGroups,    // get information about the token's groups 
         (LPVOID) ptg,   // pointer to TOKEN_GROUPS buffer
         dwLength,       // size of buffer
         &dwLength       // receives required buffer size
         )) 
   {
      goto Cleanup;
   }
Steve Townsend
  • 53,498
  • 9
  • 91
  • 140
  • See my reply to the first answer. – Phantom Sep 08 '10 at 19:07
  • @Phantom: You can never say the "first" answer, because answers are displayed in a random order. It's also affected by upvote counts. – Billy ONeal Sep 08 '10 at 19:19
  • @Phantom - you cannot avoid two calls unless you wish to make the first call with a buffer sized at some large number that ensures it is always big enough. I cannot think how you would determine what's guaranteed to be safe. Allocating large buffers here could wind up even slower than the two calls. Is it worth it? – Steve Townsend Sep 08 '10 at 19:25
  • @Steve: I'm new here. What is "upvote counts"? So, how can I refer readers to my reply to particular answer? – Phantom Sep 08 '10 at 19:28
  • 1
    @Phantom: Use the replier's handle. So you would say things like "I have put my question as a comment in Steve Townsend's answer." – dirkgently Sep 08 '10 at 19:44
  • @Phantom - upvotes and downvotes are how people show their (dis)approval of StackOverflow Qs and As. Check out the number (= upvotes minus downvotes) and up/down arrows on the LHS here. If you want to point people to individual comments, I don't know how to do that (but I see that @dirkgently does :). – Steve Townsend Sep 08 '10 at 19:45
  • @Steve: Size of "S-1-5-21-583907252-606747145-725345543-1003" is 36 in its binary form. So, 100 is guaranteed to be safe, and it's not a large number guest. – Phantom Sep 08 '10 at 19:57
  • 1
    I would say that you cannot predict how Windows is going to behave on every call you make here. This could work for months and then fail repeatedly on some unexpected input. I really don't understand why following the API rules is such a big deal. Yes they suck, but there are so many bigger deals to worry about. – Steve Townsend Sep 08 '10 at 20:00
4

Look at the last three parameters:

TokenInformation [out, optional]

A pointer to a buffer the function fills with the requested information. The structure put into this buffer depends upon the type of information specified by the TokenInformationClass parameter.

TokenInformationLength [in]

Specifies the size, in bytes, of the buffer pointed to by the TokenInformation parameter. If TokenInformation is NULL, this parameter must be zero.

ReturnLength [out]

A pointer to a variable that receives the number of bytes needed for the buffer pointed to by the TokenInformation parameter. If this value is larger than the value specified in the TokenInformationLength parameter, the function fails and stores no data in the buffer.

If the value of the TokenInformationClass parameter is TokenDefaultDacl and the token has no default DACL, the function sets the variable pointed to by ReturnLength to sizeof(TOKEN_DEFAULT_DACL) and sets the DefaultDacl member of the TOKEN_DEFAULT_DACL structure to NULL.

Since you don't know how big a buffer you need to pass for parameter #2, you need to query the API for the exact size. And then you pass-in a sufficiently large buffer and get back the information you want.

You could always guess the buffer size and it may work.

Note that this is a typical of Win32 APIs. It helps to get this idiom right once and for all.

Community
  • 1
  • 1
dirkgently
  • 108,024
  • 16
  • 131
  • 187
  • I don't want to call it twice. BTW, see my reply to the first answer. – Phantom Sep 08 '10 at 19:10
  • 4
    @Phantom: You have no choice. For well defined behavior you *must* call it twice. – Billy ONeal Sep 08 '10 at 19:19
  • 1
    @Phantom: Why not. It is the DOCUMENTED and recommended way to do this. It won't be a heavy function call – Gerry Coll Sep 08 '10 at 21:15
  • @Gerry: I recently found in MSDN that the sid maximum length is 68 bytes in its binary form. I already quoted the link in my reply to Rob Kennedy's answer. – Phantom Sep 09 '10 at 08:30
2

You can make this work in the first call if your buffer is large enough. Most code that use these methods will try first with a fixed size buffer and then allocate a larger buffer if the call indicated that it needs more memory.

Mike
  • 3,462
  • 22
  • 25