How to Check Access Rights

Given that everyone is always pushing for better security mechanisms, I’m always surprised at how incredibly difficult the simple tasks can be in the Win32 security model. At work, we have an application that wants to do the right thing — it wants to show a UAC shield badge on a menu item, because that menu may require elevated privileges in order to function properly. However, it may not require elevation either — the menu is used to copy a file to a particular pre-defined directory. So we want to show the UAC badge only if necessary. So how do you do that?

The short answer is: with the GetFileSecurityand AccessCheck APIs. Of course, this would be a boring blog post if that was all there was to it.

The trick to checking whether a folder is accessible or not boils down to how many magic incantations you can figure out from the incredibly sparse documentation, and absolutely woeful “examples” on MSDN. The real keys boil down to this: understanding impersonation tokens, understanding the GENERIC_MAPPING structure, and understanding how AccessCheck works.

Impersonation tokens are quite easy — you open up a token to the process, and duplicate the token with the request for the duplicated token to be an impersonation token. Why is this required? Because the process token is a primary token, it cannot be used to ask questions about a particular user. So you make an impersonation token because you are pretending to be the user.

The GENERIC_MAPPING structure is something that I think most people struggle with. There is almost no documentation available for what this is, or how to use it. But at the end of the day, it’s actually a simple concept. Every HANDLE object in the system has specific access rights associated with it. You’ll often-times see them listed in the API documentation. For instance, the call to OpenProcessToken requires the process to have the PROCESS_QUERY_INFORMATION rights. However, most users are used to using generic access rights, like GENERIC_READ or GENERIC_WRITE. What the GENERIC_MAPPING structure does is allow you to map from the generic rights to the specific rights. So you fill out the map using the specific rights you want (in this case, the FILE rights such as FILE_GENERIC_READ), and then use the MapGenericMask function to modify your GENERIC_READ into the proper form.

The final task is to understand what AccessCheck does. It walks over the list of access control lists (or ACLs) in the security object you get back from GetFileSecurity, and looks for ones which specifically allow you access to the rights your are querying about. As it’s processing rights, it keeps a tally of the ones that are allowed, and the ones that are not allowed. At the end of the ACL list, if there’s nothing that’s on the not allowed list, then you have access to it. But it still tells you the things you were explicitly allowed access to, so long as you asked about them in your mapping. This means AccessCheck can actually do two different things for you. One is check whether you’ve got access to perform a particular operation. The other is to tell you what you’re allowed to do if you ask for MAXIMUM_ALLOWED access rights.

Since I wanted to set out to write a function that checks a folder to see if you can perform a read or write operation, I want to use AccessCheck the first way, not the second. So my code ended up looking like this:

bool CanAccessFolder( LPCTSTR folderName, DWORD genericAccessRights )
{
	bool bRet = false;
	DWORD length = 0;
	if (!::GetFileSecurity( folderName, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION 
			| DACL_SECURITY_INFORMATION, NULL, NULL, &length ) && 
			ERROR_INSUFFICIENT_BUFFER == ::GetLastError()) {
		PSECURITY_DESCRIPTOR security = static_cast< PSECURITY_DESCRIPTOR >( ::malloc( length ) );
		if (security && ::GetFileSecurity( folderName, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION
							| DACL_SECURITY_INFORMATION, security, length, &length )) {
			HANDLE hToken = NULL;
			if (::OpenProcessToken( ::GetCurrentProcess(), TOKEN_IMPERSONATE | TOKEN_QUERY | 
					TOKEN_DUPLICATE | STANDARD_RIGHTS_READ, &hToken )) {
				HANDLE hImpersonatedToken = NULL;
				if (::DuplicateToken( hToken, SecurityImpersonation, &hImpersonatedToken )) {
					GENERIC_MAPPING mapping = { 0xFFFFFFFF };
					PRIVILEGE_SET privileges = { 0 };
					DWORD grantedAccess = 0, privilegesLength = sizeof( privileges );
					BOOL result = FALSE;

					mapping.GenericRead = FILE_GENERIC_READ;
					mapping.GenericWrite = FILE_GENERIC_WRITE;
					mapping.GenericExecute = FILE_GENERIC_EXECUTE;
					mapping.GenericAll = FILE_ALL_ACCESS;

					::MapGenericMask( &genericAccessRights, &mapping );
					if (::AccessCheck( security, hImpersonatedToken, genericAccessRights, 
							&mapping, &privileges, &privilegesLength, &grantedAccess, &result )) {
						bRet = (result == TRUE);
					}
					::CloseHandle( hImpersonatedToken );
				}
				::CloseHandle( hToken );
			}
			::free( security );
		}
	}

	return bRet;
}

You use the function by passing in the path to a file or folder, and one or more of the GENERIC flags, like this:

if (CanAccessFolder( TEXT( "C:\\Users\\" ), GENERIC_WRITE )) {}
if (CanAccessFolder( TEXT( "C:\\" ), GENERIC_READ | GENERIC_WRITE )) {}

So if you’re struggling with the terrible documentation on this subject, hopefully I’ve helped to shed a bit of light on it for you. Or at least given you some code that will let you check the access rights to a file or folder.

This entry was posted in Win32 and tagged , . Bookmark the permalink.

17 Responses to How to Check Access Rights

  1. Keith says:

    This is an interesting code sample. I tried it out myself cause I needed to find out if I had write and delete permissions on a network share.
    However it always appeared to return true (even though I know I do not have delete access) explorer would give me an access denied. Any ideas would be appreciated.. Thanks

  2. Aaron Ballman says:

    In order to test out delete functionality, you’d have to map it through one of the generic masks. I noticed that I am using FILE_GENERIC_WRITE, which tests whether you can write data. But that bitmask does not include FILE_DELETE_CHILD, which is what you’d need to test against.

    That’s my best guess though — I don’t have access to a network (or local) folder to test it against though…

  3. mathieu cupryk says:

    Do you have any C++ application that use this checkaccess

  4. Aaron Ballman says:

    No, but it shouldn’t be difficult to create one. Off the top of my head:

    #include <stdio.h>
    #include <map>
    #include <string>
    
    int main( int argc, char *argv[] ) {
      if (argc != 3) {
        ::printf( "checkaccess.exe Read|Write|Execute|ReadWrite|ReadExecute|WriteExecute|ReadWriteExecute path_to_folder_or_file\n" );
      } else {
        // First arg is the application, second is the access, third
        // is the folder or file
        std::map< std::string, DWORD > m;
        m[ "Read" ] = GENERIC_READ;
        m[ "Write" ] = GENERIC_WRITE;
        // Etc
        
        auto iter = m.find( argv[ 1 ] );
        if (iter != m.end()) {
          if (CheckAccess( argv[ 2 ], iter->second )) {
          	::printf( "Do have access\n" );
          } else {
            ::printf( "Do not have access\n" );
          }
        }
      }
      return 0;
    }
    

    I’ve not tested it, nor completed it even (you still have to add the rest of the mappings), but you get the idea.

  5. Libor Chocholaty says:

    Hi Aaron,

    I tried to use this approach to check accessibility to write file from IE MIME plugin which runs with low integrity level. Your function always returns false to check for GENERIC_WRITE even if the requested folder was the right one C:\Users\\AppData\Local\Temp\Low\ and finally plugin created the file there and was able to wrote into it.
    What is the reason for this behavior?

  6. Aaron Ballman says:

    @Libor — I’m not certain why it would behave that way, truth be told. I think you’d have to look at the ACL for the folder on that machine to see what access rights are granted to it. Perhaps IE is interacting with the low integrity plugin in some fashion prior to the creation of the file? I’ve not done anything with IE plugins, so I’m just wildly guessing.

  7. MiniEggs2 says:

    Hi I’m trying to get this to work without success hopefully you can give me a pointer

    I’ve posted my problem on MSDN here
    http://social.msdn.microsoft.com/Forums/en-US/cc4dc47f-45a2-4e1b-8071-ae091b546307/accesscheck-problem

    Thanks in advance

  8. Vishwa says:

    Thank you very much Aaron. It worked for me. Prior to this, I struggled for 8 hours without success

  9. Sanju says:

    The code seems to be working fine on Win7 & below. However, its not working properly on Win8 and above. AccessCheck is returning true, and result is also true, when am checking for GENERIC_WRITE on a folder where I don’t have write access, only read access is there. If I turn off the read access also, then GetFileSecurity returns false and saves the day. But the write access check is not working. I don’t know what makes this non-conformable with win8.

  10. Andrea says:

    Hi,
    This function is gold!
    Thanks for sharing.
    By the way it is not working for paths that involve just a mapped drive.
    For instance I have a mapped drive “pippo” that I can reach with : \\pippo
    The function is working for the directory : \\pippo\VeryImportantDirectory
    but it is not working for : \\pippo
    Any idea how to extend the functionality? I have no enough experience in windows :(

  11. Aaron Ballman says:

    @Andrea — The \\Server construct names the server, not a folder on the server. You need to include the share (\\Server\VeryImportantDirectory) in order to check the access control list because the server itself does not have an ACL (that I’m aware of).

    @Sanju — I don’t have access to a Windows 8 machine currently, so I’m not certain what would have changed. I’d be surprised if this functionality broke with Windows 8. If I have the chance to spin up a VM, I’ll look into it.

  12. Frank says:

    Hello, Aaron,
    the codes works for me … Great! But with one exception:
    If the folder is a network share like “\\Server\Share” and in the filesystem behind i have all rights (Write, Read) but in the share permission i have only read access (no write access) your function returns for CanAccessFolder(“\\Server\Share “, GENERIC_WRITE) the result true … but it should be false, because of the missing share write permission. When i try to copy a file on the share it is denied … as expected. It seems that the file permission is checked but not the share permission. Do you know how to modify your code to take account of the share permissions? Or how to check the file permission?

  13. Frank says:

    ähhh … typo: … Or how to check the SHARE permission?

  14. Frank says:

    I have found the solution: The function GetNamedSecurityInfo() (instead of GetFileSecurity()) is my friend …. with the parameter SE_LMSHARE as 2nd parameter … ;-)
    Thanx again for your nice function … it saves me a lot of time!

  15. I rarely need to go near ACLs. I agree the documentation and examples aren’t wonderful.
    This helped a lot. Thanks.

  16. Philip says:

    This worked well for me. Thanks!

  17. Yony says:

    FINALLY…. Sorry, But only the following code is working on remote shared folders
    such as \\Server\share\directory\directory, etc… and checks write permissions…
    Working on Windows 7, VS 2012

    BOOL GetFileSD(wstring strFile, PSECURITY_DESCRIPTOR *pFileSD, PACL *pACL)
    {
    BOOL bRetVal = FALSE;
    DWORD dwErr = 0;
    SECURITY_INFORMATION secInfo = DACL_SECURITY_INFORMATION;

    if (strFile.length() == 0)
    {
    cout << "ERROR: empty file name specified.\n" << endl;
    return FALSE;
    }

    HANDLE hFile = ::CreateFile(strFile.c_str(), READ_CONTROL, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
    PrintWinError();
    return FALSE;
    }

    dwErr = ::GetSecurityInfo(hFile, SE_FILE_OBJECT, secInfo, NULL, NULL, pACL, NULL, pFileSD);
    if (dwErr != ERROR_SUCCESS)
    {
    PrintWinError(dwErr);
    ::CloseHandle(hFile);
    return FALSE;
    }

    ::CloseHandle(hFile);

    return TRUE;
    }

    BOOL CheckPermissionsOnFile(wstring strFileName, DWORD genericAccessRights)
    {
    BOOL bRet = FALSE;

    if (strFileName.length() < 1)
    return FALSE;

    PACL pFileDACL = NULL;
    PSECURITY_DESCRIPTOR pFileSD = NULL;
    BOOL bRetVal = GetFileSD(strFileName, &pFileSD, &pFileDACL);
    if (FALSE == bRetVal)
    {
    cout << L"ERROR: Failed to get file SID\n" << endl;
    goto CleanUp;
    }

    // If NULL DACL is present for the file that means that all access is present for this file. Therefore user has all the permissions.
    if (NULL == pFileDACL)
    {
    bRet = TRUE;
    goto CleanUp;
    }

    HANDLE hToken = NULL;
    if (::OpenProcessToken( ::GetCurrentProcess(), TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ, &hToken ))
    {
    HANDLE hImpersonatedToken = NULL;
    if (::DuplicateToken( hToken, SecurityImpersonation, &hImpersonatedToken ))
    {
    GENERIC_MAPPING mapping = { 0xFFFFFFFF };
    PRIVILEGE_SET privileges = { 0 };
    DWORD grantedAccess = 0, privilegesLength = sizeof( privileges );
    BOOL result = FALSE;

    mapping.GenericRead = FILE_GENERIC_READ;
    mapping.GenericWrite = FILE_GENERIC_WRITE;
    mapping.GenericExecute = FILE_GENERIC_EXECUTE;
    mapping.GenericAll = FILE_ALL_ACCESS;

    ::MapGenericMask( &genericAccessRights, &mapping );

    if (::AccessCheck( pFileSD, hImpersonatedToken, genericAccessRights, &mapping, &privileges, &privilegesLength, &grantedAccess, &result ))
    {
    bRet = (result == TRUE);
    }
    ::CloseHandle( hImpersonatedToken );
    }
    ::CloseHandle( hToken );
    }

    CleanUp:
    if (pFileSD != NULL)
    {
    LocalFree(pFileSD);
    }
    return bRet;
    }

Leave a Reply

Your email address will not be published. Required fields are marked *