devxlogo

Retrieving NTFS Permissions with C++

Retrieving NTFS Permissions with C++

icrosoft originally designed NTFS (New Technology File System) for its NT series of operating systems. Over time, NTFS was improved and it is now being used on the newer server OSs: Windows 2000/XP/2003. Compared to FAT32, NTFS opened up a whole new world of file management.

One of the key features of NTFS is the ability to define access control information for each file system object?NTFS security. By applying different security policies, you may allow or deny access to files and folders for particular users or groups.

In this article, I will show how to use appropriate Win32 APIs to programmatically query NTFS security information, which you can then use in your applications however you like.

Storing and Managing Permissions
The file system driver is the nucleus of any file system. It manages all file system requests?creating new files, opening existing files, writing to files etc. The file system driver is a mediator between the operating system and the storage device drivers.

The NTFS driver controls access to the file system according to specified permissions. These permissions are expressed by Access Control Lists (ACLs). There are two types of ACLs: security and discretionary. The former (SACL) deals with permissions for auditing secured objects and is out of scope of this article. The latter (DACL) is designed to specify permissions on different objects such as files, folders etc. In this article, we will work with DACLs only. ACLs are composed of Access Control Entries (ACEs). Each ACE allows or denies specific permissions (for example, by user or group) to or from a secure entity. ACE uses Security Identifiers (SIDs) that uniquely identify any user or group in an NT-based system or network.

Windows provides an easy and intuitive GUI for setting permissions. You may view the specified permissions for any file or folder in the Security tab of the Properties dialog box in Windows Explorer (see Figure 1).

Permissions Precedence
ACEs are divided into two categories: allowed and denied. But it is not unusual for there to be conflicting ACEs for a given user or group. For example, some users may appear in several groups simultaneously. The NTFS security module should combine specified permissions in different ACEs and decide whether to allow or deny an advanced permission to a user, group, or other security entity (identified by SID). Figure 4 shows the diagram in the form of a state machine, which illustrates the NTFS decision mechanism.
The ACLInfo Sample
The sample program that accompanies this article is a console application that accepts a file or directory path as its command line argument and then retrieves and displays the ACL information for that file. This will only work correctly if the file or folder resides on an NTFS-formatted partition and you have Read access permissions to ACL information.

Listing 1 shows the interface definition of the ACLInfo class as it appears in the ACLInfo.h header file. You should construct an ACLInfo object supplying the path to the file or folder. Then call the Query method that loads the corresponding ACEs and places them into the linked list (m_sAceList) of the ace_list* type (the definition of ace_list appears in Listing 1 as well). Finally, call the Output method to display the ACL. The Main.cpp file (Listing 2) is the sample usage of the ACLInfo class.

The Query Method
The Query method (Listing 3) calls the GetFileSecurityW function twice. In the first call it obtains the size required to store the security descriptor. Then you allocate the required memory and call the function again, this time supplying the pointer to the BYTE* buffer that will hold the security descriptor. Note that I pass DACL_SECURITY_INFORMATION as the second argument. This way, I inform the API that I want Discretionary Access Control List information. The second argument may be any valid combination of different flags that specify the type of security information you are querying. Read the MSDN documentation for GetFileSecurity to learn more about its arguments.

The next fragment retrieves DACL from the security descriptor:

   // Getting DACL from Security Descriptor   PACL pacl;   BOOL bDaclPresent, bDaclDefaulted;   bSuccess =GetSecurityDescriptorDacl(      (SECURITY_DESCRIPTOR*)pSecDescriptorBuf,      &bDaclPresent, &pacl, &bDaclDefaulted);         // Check if we successfully retrieved DACL   if (!bSuccess)   {      DWORD dwError = GetLastError();      cout 

This code checks that DACL is actually present in the security descriptor. If there is no DACL present then GetSecurityDescriptorDacl sets bDaclPresent to False.

   // Check if DACL present in security descriptor   if (!bDaclPresent)   {      cout 

After verifying that DACL was successfully retrieved you can iterate through its ACEs and fill in the linked list from ACLInfo. The following loop does just this:

   for (USHORT i = 0; i AceCount; i++)   {      LPVOID pAce;      bSuccess = GetAce(pacl, i, &pAce);      if (!bSuccess)      {         DWORD dwError = GetLastError();         cout 

The Output Method
Most of the job of interpreting the queried ACEs is done in the Output method. Each node of m_sAceList has bAllowed field, which defines whether the stored ACE allows or denies permissions. Recall that the information about ACE type resides in its header represented by the ACE_HEADER structure. Knowing ACE type is not enough, because we have to know what exact permissions it allows or denies. At this stage, the AccessMask field comes to our aid. This field is present in both the ACCESS_ALLOWED_ACE and ACCESS_DENIED_ACE structures. It is just a combination (bitwise OR) of permission flags. Any advanced permission (those that appear in Figure 3) has its own bit. Examine the AccessMask field to see what permissions are allowed or denied by an ACE.

Visual C++ header files have predefined macros for advanced permission flags, such as FILE_READ_DATA, FILE_EXECUTE, etc. There are also macros that represent frequently used combinations of permission flags (FILE_GENERIC_READ, FILE_GENERIC_WRITE).

In the ACLInfo sample, I defined my own combinations for read, write, and execute permissions:

   #define READ_PERMISSIONS (FILE_READ_DATA |      FILE_READ_ATTRIBUTES)      #define WRITE_PERMISSIONS (FILE_WRITE_DATA |       FILE_APPEND_DATA |       FILE_WRITE_ATTRIBUTES |       FILE_WRITE_EA)      #define EXECUTE_PERMISSIONS (FILE_READ_DATA |       FILE_EXECUTE)

There are two different checks for allowed and denied ACE. When I examine an allowed ACE, this code executes:

   // For Allowed aces   if (pList->bAllowed)   {      // Read Permissions      if ((maskPermissions & READ_PERMISSIONS) ==          READ_PERMISSIONS)      {         os 

The above check assures that an allowed ACE has all the READ_PERMISSIONS flags turned on.

In the case of denied ACE, the check is opposite?if at least one flag of READ_PERMISSIONS is turned on, I decide that ACE denies reading:

   . . .   else   // Denied Ace permissions   {      // Read Permissions      if ((maskPermissions & READ_PERMISSIONS) != 0)      {         os 

I perform similar checks for write and execute permissions.

Extracting Names
It might sometimes be useful to extract the user or group name from an ACE. The following code fragment retrieves an SID that is stored in ACE (the bold lines):

   pAce = pList->pAce;   if (pList->bAllowed)   {      ACCESS_ALLOWED_ACE* pAllowed =         (ACCESS_ALLOWED_ACE*)pAce;      pAceSid = (SID*)(&(pAllowed->SidStart));      maskPermissions = pAllowed->Mask;   }   else   {      ACCESS_DENIED_ACE* pDenied = (ACCESS_DENIED_ACE*)pAce;      pAceSid = (SID*)(&(pDenied->SidStart));      maskPermissions = pDenied->Mask;   }

Having SID in hand, we may successfully obtain account information using the LookuAccounSid API call as shown below:

   DWORD dwCbName = 0;   DWORD dwCbDomainName = 0;   SID_NAME_USE SidNameUse;   TCHAR bufName[MAX_PATH];   TCHAR bufDomain[MAX_PATH];   dwCbName = sizeof(bufName);   dwCbDomainName = sizeof(bufDomain);      // Get account name for SID   BOOL bSuccess = LookupAccountSid(NULL, pAceSid,      bufName, &dwCbName, bufDomain,       &dwCbDomainName, &SidNameUse);   if (!bSuccess)   {      cout 

LookupAccountSid stores user/group name into bufName and domain name into bufDomain. SidNameUse is populated with a type of security entity that SID represents (user, group, well-known group etc.)

In your programs, you may choose a different interpretation of the information stored in ACLs. Just remember that the account under which these programs run must have sufficient permissions to read permissions.

The Sample Run
I ran the ACLInfo.exe program specifying the path to the file that resides on my NTFS partition (disk G) and obtained the following output:

   D:ArticlesDevXNTFSACLInfoVS_7Debug>ACLInfo.exe       "g:my documentsspecs.txt"      Allowed to: BUILTINUsers [R X]   Allowed to: LOTUSYevgeny Menaker [RWX]   Allowed to: NT AUTHORITYSYSTEM [RWX]   Allowed to: BUILTINAdministrators [RWX]   Denied from: LOTUSYevgeny Menaker [  X]   Denied from: LOTUSRob [R X]

Note that paths containing spaces should be enclosed by double quotes in the command line.

You should now have a working knowledge of how to use Win32 APIs to query security information from NTFS objects (files and folders). You may expand the supplied code to fit your needs and throw the heavy task of managing user permissions onto NTFS. Many products, such as Microsoft IIS already employ this technique. Now you can too.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist