locked
LookupAccountSid() fails with strange errors in a thread that is impersonating a pipe client RRS feed

  • Question

  • I create the pipe in the server application in the following way:

    hPipe=CreateNamedPipe(szPipeName,PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE | WRITE_DAC,PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE,1,0,0,0,0);
    SetSecurityInfo(hPipe,SE_KERNEL_OBJECT,DACL_SECURITY_INFORMATION,0,0,0,0);	//Allow access for any client

    In a new thread I start a connection-waiting loop:

    DWORD dwErr;bool bContinue=1;
    while(bContinue){
    	ConnectNamedPipe(hPipe,0);
    	dwErr=GetLastError();
    	switch(dwErr){
    		case 0:{
    //Some reading with PeekNamedPipe()/ReadFile() if(ImpersonateNamedPipeClient(hPipe)){ HANDLE hToken;
    if(OpenThreadToken(GetCurrentThread(),TOKEN_READ,0,&hToken)){
    DWORD dwBytes; GetTokenInformation(hToken,TokenUser,0,0,&dwBytes); dwErr=GetLastError(); if(dwErr==ERROR_INSUFFICIENT_BUFFER){ DWORD dwDNLen=0,dwUNLen=0;SID_NAME_USE eNameUse; TOKEN_USER *pTU=(TOKEN_USER*)MemAlloc(dwBytes); GetTokenInformation(hToken,TokenUser,pTU,dwBytes,&dwBytes); LookupAccountSid(0,pTU->User.Sid,0,&dwUNLen,0,&dwDNLen,&eNameUse); dwErr=GetLastError(); if(dwErr==ERROR_INSUFFICIENT_BUFFER){ PWCH pszDN=(PWCH)MemAlloc((dwDNLen+dwUNLen)*sizeof WCHAR),pszUN=pszDN+dwDNLen; LookupAccountSid(0,pTU->User.Sid,pszUN,&dwUNLen,pszDN,&dwDNLen,&eNameUse); //Further using of domain and user names
    MemFree(pszDN); }else{ //Handle LookupAccountSid() error } MemFree(pTU); }else{ //Handle GetTokenInformation() error }
    CloseHandle(hToken); }else{ //Handle OpenThreadToken() error } RevertToSelf(); }else{ //Handle ImpersonateNamedPipeClient() error } DisconnectNamedPipe(hPipe); }break; case ERROR_OPERATION_ABORTED:{ bContinue=0; //Finish waiting for connection }break; default:{ //Handle ConnectNamedPipe() error
    } }
    }

    In the client, I connect to the pipe as follows:

    //uIL is a combination of SECURITY_SQOS_PRESENT, SECURITY_IMPERSONATION or SECURITY_DELEGATION (varies of which I choose) and optionally SECURITY_EFFECTIVE_ONLY
    HANDLE hPipe=CreateFile(pszFullPipeName,GENERIC_READ | GENERIC_WRITE,0,0,OPEN_EXISTING,uIL,0);
    if((int)hPipe!=-1){
    	DWORD dwMode=PIPE_READMODE_MESSAGE;
    	SetNamedPipeHandleState(hPipe,&dwMode,0,0);
    	WriteFile(hPipe,…);
    	CloseHandle(hPipe);
    }else{
    	//Handle CreateFile() error
    }

    This codes run on the same machine, and a server part of pszFullPipeName contains localhost, for example, or my computer name.
    For some combinations of flags in uIL and users running server and client (ordinary user, administrator, elevated administrator) after LookupAccountSid() I get RPC_S_SERVER_UNAVAILABLE or ERROR_ACCESS_DENIED, and only in 2 or 3 cases this call succeeds. So I have some questions:
    0) how to explain RPC_S_SERVER_UNAVAILABLE in LookupAccountSid()?
    1) how to explain ERROR_ACCESS_DENIED in LookupAccountSid()?
    2) what should I do to have LookupAccountSid() succeeded?
    3) when I use FormatMessage() for RPC_S_SERVER_UNAVAILABLE, it returns ERROR_MUI_FILE_NOT_FOUND. Why, how is it possible, although in other cases it works? (this question is not so important, but I would like to know the answer)


    If I fall I will arise on my way to paradise


    Sunday, July 23, 2017 6:46 PM

All replies

  • Hi apixosoft,

    Thank you for posting here,

    >>" 0) how to explain RPC_S_SERVER_UNAVAILABLE in LookupAccountSid()?  "

    According to the error message and from the document we know that the RPC server is unavailable. Before using the method, please check whether the serve exists.

    >>" 1) how to explain ERROR_ACCESS_DENIED in LookupAccountSid()? "

    I think that the calling process is not the owner of the object, did you try to use the administrator to run the application.

    >>" 2) what should I do to have LookupAccountSid() succeeded? "

    The LookupAccountSid function attempts to find a name for the specified SID by first checking a list of well-known SIDs. If the supplied SID does not correspond to a well-known SID, the function checks built-in and administratively defined local accounts. Next, the function checks the primary domain.

    You can reference the demo to modify your current project.

    The following example uses the  GetSecurityInfo and  LookupAccountSid functions to find and print the name of the owner of a file. The file exists in the current working directory on the local server.

    Best  Regards,

    Hart


    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Monday, July 24, 2017 5:36 AM
  • I wonder that I'm getting obvious information which I have read on MSDN already.

    > Before using the method, please check whether the serve exists
    Which server? Client and server are both running locally, and even if I run it on different machines, of course the server will be available for the clietn, otherwise it won't connect.
    If you mean the server in LookupAccountSid(), then there is no any server specified, as you may see.
    So which server should I check for availability, and where from originates that RPC communication?

    > I think that the calling process is not the owner of the object, did you try to use the administrator to run the application
    Yes, I tried. User name resolves only when ImpersonationLevel is SecurityDelegation, and the server runs as Administrator or as the same user which runs the client.
    Of which object exactly? If you mean the pipe, then its DACL is set to zero, as you may see in my code. How else should I modify my code to allow more access to the object?

    > You can reference the demo to modify your current project
    The demo is useless in this situation at least because of different context L


    If I fall I will arise on my way to paradise

    • Edited by apixosoft Monday, July 24, 2017 7:26 AM
    Monday, July 24, 2017 7:23 AM
  • Generally it is a logic error to call GetLastError unless the documentation for a function indicates that additional error information can be obtained if a Win32 API function call failed.  For example, the success/failure of LookupAccountSid is not determined before calling GetLastError.

    The code also assumes that a successful call to ConnectPipe sets the thread's error code to ERROR_SUCCESS.  At least on Win 8.1, this is not the case.  Again, the success/failure of the function is not checked  before calling GetLastError.



    • Edited by RLWA32 Monday, July 24, 2017 11:35 AM
    Monday, July 24, 2017 11:31 AM
  • I've added a check, and it does return FALSE, so GetLastError() call is required to understand the nature of an error. It means that 0x5 is related to LookupAccountSid(), not something else.

    BTW, why is it a logic error in other cases? If a function call succeeds, GetLastError() will return 0 as far as I know.


    If I fall I will arise on my way to paradise

    Monday, July 24, 2017 11:37 AM
  • IBTW, why is it a logic error in other cases? If a function call succeeds, GetLastError() will return 0 as far as I know.

    From GetLastError function

    "The Return Value section of the documentation for each function that sets the last-error code notes the conditions under which the function sets the last-error code. Most functions that set the thread's last-error code set it when they fail. However, some functions also set the last-error code when they succeed. If the function is not documented to set the last-error code, the value returned by this function is simply the most recent last-error code to have been set; some functions set the last-error code to 0 on success and others do not."

    Unless the documentation states that the last-error code is set on success it is a logic error to assume that it is.
    • Edited by RLWA32 Monday, July 24, 2017 11:45 AM
    Monday, July 24, 2017 11:44 AM
  • Thanks. Ok, now we see that it is not "the most recent last-error code" from some other function, and is caused by LookupAccountSid(), so my above questions still being actual.

    If I fall I will arise on my way to paradise

    Monday, July 24, 2017 11:48 AM
  • I suggest you share a small demo that reliably reproduces the issues.
    Monday, July 24, 2017 11:52 AM
  • Well, if my codelines weren't sufficient, then here is the code ☺

    The server is passive, so you just have to watch an output when client connects. Almost all information is unimportant except user SID (which is shown if there was an error in LookupAccountSid) or user name (if there was no error).
    In the client you need to fill the 1st field with target computer name (may be localhost or 127.0.0.1 for local test), the 2nd field with any non-empty message, and choose impersonation level, then press Enter.

    As I have mentioned, LookupAccountSid works almost always when Delegation is chosen (for local tests). But if you start the client under elevated admin, and the server — under limited admin, you'll see 0x5. Also you'll see it when the client is running under ordinary user, and the server is running as limited admin. If you choose Impersonation mode, it returns 0x5 always, although interaction is local, not remote.

    Sorry for my code, this is for test purposes only.


    If I fall I will arise on my way to paradise

    Monday, July 24, 2017 12:14 PM
  • Again, part of the problem is that you don't handle GetLastError properly.  For example, a call to LookupAccountSid can fail with ERROR_ACCESS_DENIED, but when the code finds that the error code is not for an insufficient buffer it proceeds to process garbage.  I inserted the success/failure check.

    if(!LookupAccountSid(szCompName,pTU->User.Sid,0,&dwUNLen,0,&dwDNLen,&eNameUse))
    	dwErr=GetLastError();
    if(dwErr==ERROR_INSUFFICIENT_BUFFER){
    	PWCH pszDN=(PWCH)MemAlloc((dwDNLen+dwUNLen)*sizeof WCHAR),pszUN=pszDN+dwDNLen,pszFullUserName=(PWCH)MemAlloc((dwDNLen+dwUNLen+4)*sizeof WCHAR);
    	if (!LookupAccountSid(szCompName, pTU->User.Sid, pszUN, &dwUNLen, pszDN, &dwDNLen, &eNameUse))
    		dwErr = GetLastError();
    	wsprintf(pszFullUserName,L"%s\\%s\r\n",pszDN,pszUN);
    	MemFree(pszDN);
    	AddText(pszFullUserName);
    	MemFree(pszFullUserName);
    }else{
    	PWCH pszErrInfo,pszSID,pszErrMsg=FormatErrorMsg(dwErr);
    	ConvertSidToStringSid(pTU->User.Sid,&pszSID);
    	pszErrInfo=(PWCH)MemAlloc((ARRAYSIZE(szSIDLookupError)+lstrlen(pszErrMsg)+lstrlen(pszSID))*sizeof WCHAR);
    	wsprintf(pszErrInfo,szSIDLookupError,dwErr,pszSID,pszErrMsg);
    	LocalFree(pszSID);
    	LocalFree(pszErrMsg);
    	AddText(pszErrInfo);
    	MemFree(pszErrInfo);
    }
    

    Monday, July 24, 2017 2:36 PM
  • Small goof -- the wrong parameter is passed to retrieve the TOKEN_ELEVATION_TYPE.  It should be TokenElevationType, not TokenElevation.

    And the Client's security context array omitted anonymous so the array indexes did not match up to the proper constants

    *pszImpersonationLevel[]={L"Anonymous", L"Identification",L"Impersonation",L"Delegation"};


    It would be easier to read the code if so many things weren't all put on one line.  :)
    • Edited by RLWA32 Monday, July 24, 2017 2:41 PM
    Monday, July 24, 2017 2:40 PM
  • Oh, please don't pay attention to little things, it slows the overall progress. I already told that I checked the result of LookupAccountSid() and it DID failed with ERROR_ACCESS_DENIED. If it will fail with ERROR_INSUFFICIENT_BUFFER, it will, and it will be correctly handled further. The problem is exactly in LookupAccountSid()→ERROR_ACCESS_DENIED.

    Did you try to run and check it, even with your corrections?


    If I fall I will arise on my way to paradise

    Monday, July 24, 2017 2:42 PM
  • Small goof -- the wrong parameter is passed to retrieve the TOKEN_ELEVATION_TYPE.  It should be TokenElevationType, not TokenElevation.

    Thank you for your note! It explains one small strange thing I've seen as the result of my mistake :)

    And the Client's security context array omitted anonymous so the array indexes did not match up to the proper constants

    It was omitted specially, because you cannot connect and impersonate with Anonymous level ;-)
    I even could remove an Identification level from the client because it is useless here.


    If I fall I will arise on my way to paradise

    • Edited by apixosoft Monday, July 24, 2017 2:49 PM
    Monday, July 24, 2017 2:45 PM
  • One quick observation that makes sense to me is that LookupAccountSid fails with access denied when the client impersonation level is either anonymous or  Identifcation.  I'm still playing with the code...
    Monday, July 24, 2017 2:59 PM
  • As much as I would like to help unravel the mystery there is probably more expertise in the Application Security for Windows Desktop forum
    • Proposed as answer by Baron Bi Wednesday, September 20, 2017 9:32 AM
    Monday, July 24, 2017 3:32 PM
  • Thank you for advices! Should I start a new topic there referencing this one, or there is another option (like moving the thread there)?
    BTW, soon I'll make a relation matrix to see how combination of conditions results in the program behavior.

    If I fall I will arise on my way to paradise

    Monday, July 24, 2017 7:48 PM
  • Hi,

    According to the MSDN policy, The new issue must start a new thread.  the original thread must be closed by marking useful posts as answer.

    Best Regards,

    Hart


    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Tuesday, July 25, 2017 7:02 AM
  • I'd try moving the RevertToSelf call right after OpenThreadToken, so that the thread is no longer impersonating when it calls LookupAccountSid.

    Sunday, July 30, 2017 6:26 AM