views:

147

answers:

5

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

+1  A: 

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
'try first with a fixed size bugger' - always something new to learn about Win32, eh?
Steve Townsend
@Steve hope that typo made you smile. :)
Mike
That is, how can I guess safely for the maximum buffer required? BTW, see my reply to the first answer.
Phantom
@Phantom: You can't. You have to try a larger buffer, and if it's not big enough, use a larger one.
Billy ONeal
+3  A: 

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
See my reply to the first answer.
Phantom
@Phantom: On my screen, **this is the first answer** (because answer ordering is random)
Billy ONeal
+3  A: 

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
See my reply to the first answer.
Phantom
@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
@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
@Steve: I'm new here. What is "upvote counts"? So, how can I refer readers to my reply to particular answer?
Phantom
@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
@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
@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
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
+7  A: 

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
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
@Phantom: The SID is stored in binary, not in string form. The string form is just to make it human readable.
Billy ONeal
Oops, sorry, the 43 characters is returned by ConvertSidToStringSid(). So, 36 is the SID in binary from, isn't it? Now I understand.
Phantom
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
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
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
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
@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).
Alexander
Everyone: According to the <a href="http://msdn.microsoft.com/en-us/library/cc230371%28PROT.10%29.aspx">the official Sid format documentation</a> 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
So, theoritically, there is no Sid longer than 68 bytes.
Phantom
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
I quote it from <a href="http://stackoverflow.com/questions/1140528/what-is-the-maximum-length-of-a-sid-in-sddl-format">here</a>
Phantom
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
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
And what if Microsoft changes these limits in a later version of Windows?
Gerry
@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
@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.
Alexander
+4  A: 

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.

dirkgently
I don't want to call it twice. BTW, see my reply to the first answer.
Phantom
@Phantom: You have no choice. For well defined behavior you *must* call it twice.
Billy ONeal
@Phantom: Why not. It is the DOCUMENTED and recommended way to do this. It won't be a heavy function call
Gerry
@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