How to add an access-allowed ACE to a file (102102)



The information in this article applies to:

  • Microsoft Win32 Application Programming Interface (API), when used with:
    • the operating system: Microsoft Windows NT 4.0
    • the operating system: Microsoft Windows 2000
    • the operating system: Microsoft Windows XP

This article was previously published under Q102102

SUMMARY

This article demonstrates how to add an "access-allowed" access control entry (ACE) to a file.

MORE INFORMATION

When an access-allowed ACE is added to a file's discretionary access control list (DACL), the corresponding user or group account associated with the ACE will be provided with the allowed access to the file by the system. In most cases, the file's DACL is not large enough to add an additional ACE. Therefore, it is necessary to create a new access control list (ACL) and copy the ACEs from the file's existing DACL in the preferred order. The new DACL can then replace the old DACL in the file's security descriptor (SD). This process is explained in more detail in the sample code below:

For Windows NT versions 4.0 and earlier, the preferred order of ACEs is simple. In a DACL, all access-denied ACEs should precede any access-allowed ACEs. For Windows 2000 or later, the proper order of ACEs is more complicated because of the introduction of object-specific ACEs and automatic inheritance.

The following describes the preferred order for Windows 2000 or later:
  • To ensure that non-inherited ACEs have precedence over inherited ACEs, place all non-inherited ACEs in a group before any inherited ACEs. This ordering ensures, for example, that a non-inherited access-denied ACE is enforced regardless of any inherited ACE that allows access.
  • Within the groups of non-inherited ACEs and inherited ACEs, order ACEs according to ACE type, as the following shows:
    • Access-denied ACEs that apply to the object itself.
    • Access-denied ACEs that apply to a subobject of the object, such as a property set or property.
    • Access-allowed ACEs that apply to the object itself
    • Access-allowed ACEs that apply to a subobject of the object
The low-level access control APIs for adding ACEs to a DACL do not enforce the preferred order. The AddAce function adds ACEs at a specified location in an ACL. The AddAccessAllowedAce function adds an ACE to the end of an ACL. So, it is the caller's responsibility to ensure that the ACEs are added in the preferred order. For more information about the order of ACEs in a DACL, visit the following Microsoft Developer Network (MSDN) Web site:

Sample code

The following sample code demonstrates the basic steps that are required to add an access-allowed ACE to a file's DACL. Steps 1-21 in the comments of the code are discussed in more detail at the end of this article.
#include <windows.h>
#include <tchar.h>
#include <stdio.h>

#define myheapalloc(x) (HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, x))
#define myheapfree(x)  (HeapFree(GetProcessHeap(), 0, x))

typedef BOOL (WINAPI *SetSecurityDescriptorControlFnPtr)(
   IN PSECURITY_DESCRIPTOR pSecurityDescriptor,
   IN SECURITY_DESCRIPTOR_CONTROL ControlBitsOfInterest,
   IN SECURITY_DESCRIPTOR_CONTROL ControlBitsToSet);

BOOL AddAccessRights(TCHAR *lpszFileName, TCHAR *lpszAccountName, 
      DWORD dwAccessMask) {

   // SID variables.
   SID_NAME_USE   snuType;
   TCHAR *        szDomain       = NULL;
   DWORD          cbDomain       = 0;
   LPVOID         pUserSID       = NULL;
   DWORD          cbUserSID      = 0;

   // File SD variables.
   PSECURITY_DESCRIPTOR pFileSD  = NULL;
   DWORD          cbFileSD       = 0;

   // New SD variables.
   SECURITY_DESCRIPTOR  newSD;

   // ACL variables.
   PACL           pACL           = NULL;
   BOOL           fDaclPresent;
   BOOL           fDaclDefaulted;
   ACL_SIZE_INFORMATION AclInfo;

   // New ACL variables.
   PACL           pNewACL        = NULL;
   DWORD          cbNewACL       = 0;

   // Temporary ACE.
   LPVOID         pTempAce       = NULL;
   UINT           CurrentAceIndex = 0;

   UINT           newAceIndex = 0;

   // Assume function will fail.
   BOOL           fResult        = FALSE;
   BOOL           fAPISuccess;

   SECURITY_INFORMATION secInfo = DACL_SECURITY_INFORMATION;

   // New APIs available only in Windows 2000 and above for setting 
   // SD control
   SetSecurityDescriptorControlFnPtr _SetSecurityDescriptorControl = NULL;

   __try {

      // 
      // STEP 1: Get SID of the account name specified.
      // 
      fAPISuccess = LookupAccountName(NULL, lpszAccountName,
            pUserSID, &cbUserSID, szDomain, &cbDomain, &snuType);

      // API should have failed with insufficient buffer.
      if (fAPISuccess)
         __leave;
      else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
         _tprintf(TEXT("LookupAccountName() failed. Error %d\n"), 
               GetLastError());
         __leave;
      }

      pUserSID = myheapalloc(cbUserSID);
      if (!pUserSID) {
         _tprintf(TEXT("HeapAlloc() failed. Error %d\n"), GetLastError());
         __leave;
      }

      szDomain = (TCHAR *) myheapalloc(cbDomain * sizeof(TCHAR));
      if (!szDomain) {
         _tprintf(TEXT("HeapAlloc() failed. Error %d\n"), GetLastError());
         __leave;
      }

      fAPISuccess = LookupAccountName(NULL, lpszAccountName,
            pUserSID, &cbUserSID, szDomain, &cbDomain, &snuType);
      if (!fAPISuccess) {
         _tprintf(TEXT("LookupAccountName() failed. Error %d\n"), 
               GetLastError());
         __leave;
      }

      // 
      // STEP 2: Get security descriptor (SD) of the file specified.
      // 
      fAPISuccess = GetFileSecurity(lpszFileName, 
            secInfo, pFileSD, 0, &cbFileSD);

      // API should have failed with insufficient buffer.
      if (fAPISuccess)
         __leave;
      else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
         _tprintf(TEXT("GetFileSecurity() failed. Error %d\n"), 
               GetLastError());
         __leave;
      }

      pFileSD = myheapalloc(cbFileSD);
      if (!pFileSD) {
         _tprintf(TEXT("HeapAlloc() failed. Error %d\n"), GetLastError());
         __leave;
      }

      fAPISuccess = GetFileSecurity(lpszFileName, 
            secInfo, pFileSD, cbFileSD, &cbFileSD);
      if (!fAPISuccess) {
         _tprintf(TEXT("GetFileSecurity() failed. Error %d\n"), 
               GetLastError());
         __leave;
      }

      // 
      // STEP 3: Initialize new SD.
      // 
      if (!InitializeSecurityDescriptor(&newSD, 
            SECURITY_DESCRIPTOR_REVISION)) {
         _tprintf(TEXT("InitializeSecurityDescriptor() failed.")
            TEXT("Error %d\n"), GetLastError());
         __leave;
      }

      // 
      // STEP 4: Get DACL from the old SD.
      // 
      if (!GetSecurityDescriptorDacl(pFileSD, &fDaclPresent, &pACL,
            &fDaclDefaulted)) {
         _tprintf(TEXT("GetSecurityDescriptorDacl() failed. Error %d\n"),
               GetLastError());
         __leave;
      }

      // 
      // STEP 5: Get size information for DACL.
      // 
      AclInfo.AceCount = 0; // Assume NULL DACL.
      AclInfo.AclBytesFree = 0;
      AclInfo.AclBytesInUse = sizeof(ACL);

      if (pACL == NULL)
         fDaclPresent = FALSE;

      // If not NULL DACL, gather size information from DACL.
      if (fDaclPresent) {    
         
         if (!GetAclInformation(pACL, &AclInfo, 
               sizeof(ACL_SIZE_INFORMATION), AclSizeInformation)) {
            _tprintf(TEXT("GetAclInformation() failed. Error %d\n"),
                  GetLastError());
            __leave;
         }
      }

      // 
      // STEP 6: Compute size needed for the new ACL.
      // 
      cbNewACL = AclInfo.AclBytesInUse + sizeof(ACCESS_ALLOWED_ACE) 
            + GetLengthSid(pUserSID) - sizeof(DWORD);

      // 
      // STEP 7: Allocate memory for new ACL.
      // 
      pNewACL = (PACL) myheapalloc(cbNewACL);
      if (!pNewACL) {
         _tprintf(TEXT("HeapAlloc() failed. Error %d\n"), GetLastError());
         __leave;
      }

      // 
      // STEP 8: Initialize the new ACL.
      // 
      if (!InitializeAcl(pNewACL, cbNewACL, ACL_REVISION2)) {
         _tprintf(TEXT("InitializeAcl() failed. Error %d\n"), 
               GetLastError());
         __leave;
      }

      // 
      // STEP 9 If DACL is present, copy all the ACEs from the old DACL
      // to the new DACL.
      // 
      // The following code assumes that the old DACL is
      // already in Windows 2000 preferred order.  To conform 
      // to the new Windows 2000 preferred order, first we will 
      // copy all non-inherited ACEs from the old DACL to the 
      // new DACL, irrespective of the ACE type.
      // 
      
      newAceIndex = 0;

      if (fDaclPresent && AclInfo.AceCount) {

         for (CurrentAceIndex = 0; 
               CurrentAceIndex < AclInfo.AceCount;
               CurrentAceIndex++) {

            // 
            // STEP 10: Get an ACE.
            // 
            if (!GetAce(pACL, CurrentAceIndex, &pTempAce)) {
               _tprintf(TEXT("GetAce() failed. Error %d\n"), 
                     GetLastError());
               __leave;
            }

            // 
            // STEP 11: Check if it is a non-inherited ACE.
            // If it is an inherited ACE, break from the loop so
            // that the new access allowed non-inherited ACE can
            // be added in the correct position, immediately after
            // all non-inherited ACEs.
            // 
            if (((ACCESS_ALLOWED_ACE *)pTempAce)->Header.AceFlags
               & INHERITED_ACE)
               break;

            // 
            // STEP 12: Skip adding the ACE, if the SID matches
            // with the account specified, as we are going to 
            // add an access allowed ACE with a different access 
            // mask.
            // 
            if (EqualSid(pUserSID,
               &(((ACCESS_ALLOWED_ACE *)pTempAce)->SidStart)))
               continue;

            // 
            // STEP 13: Add the ACE to the new ACL.
            // 
            if (!AddAce(pNewACL, ACL_REVISION, MAXDWORD, pTempAce,
                  ((PACE_HEADER) pTempAce)->AceSize)) {
               _tprintf(TEXT("AddAce() failed. Error %d\n"), 
                     GetLastError());
               __leave;
            }

            newAceIndex++;
         }
      }

      // 
      // STEP 14: Add the access-allowed ACE to the new DACL.
      // The new ACE added here will be in the correct position,
      // immediately after all existing non-inherited ACEs.
      // 
      if (!AddAccessAllowedAce(pNewACL, ACL_REVISION2, dwAccessMask,
            pUserSID)) {
         _tprintf(TEXT("AddAccessAllowedAce() failed. Error %d\n"),
               GetLastError());
         __leave;
      }

      // 
      // STEP 15: To conform to the new Windows 2000 preferred order,
      // we will now copy the rest of inherited ACEs from the
      // old DACL to the new DACL.
      // 
      if (fDaclPresent && AclInfo.AceCount) {

         for (; 
              CurrentAceIndex < AclInfo.AceCount;
              CurrentAceIndex++) {

            // 
            // STEP 16: Get an ACE.
            // 
            if (!GetAce(pACL, CurrentAceIndex, &pTempAce)) {
               _tprintf(TEXT("GetAce() failed. Error %d\n"), 
                     GetLastError());
               __leave;
            }

            // 
            // STEP 17: Add the ACE to the new ACL.
            // 
            if (!AddAce(pNewACL, ACL_REVISION, MAXDWORD, pTempAce,
                  ((PACE_HEADER) pTempAce)->AceSize)) {
               _tprintf(TEXT("AddAce() failed. Error %d\n"), 
                     GetLastError());
               __leave;
            }
         }
      }

      // 
      // STEP 18: Set the new DACL to the new SD.
      // 
      if (!SetSecurityDescriptorDacl(&newSD, TRUE, pNewACL, 
            FALSE)) {
         _tprintf(TEXT("SetSecurityDescriptorDacl() failed. Error %d\n"),
               GetLastError());
         __leave;
      }

      // 
      // STEP 19: Copy the old security descriptor control flags 
      // regarding DACL automatic inheritance for Windows 2000 or 
      // later where SetSecurityDescriptorControl() API is available
      // in advapi32.dll.
      // 
      _SetSecurityDescriptorControl = (SetSecurityDescriptorControlFnPtr)
            GetProcAddress(GetModuleHandle(TEXT("advapi32.dll")),
            "SetSecurityDescriptorControl");
      if (_SetSecurityDescriptorControl) {

         SECURITY_DESCRIPTOR_CONTROL controlBitsOfInterest = 0;
         SECURITY_DESCRIPTOR_CONTROL controlBitsToSet = 0;
         SECURITY_DESCRIPTOR_CONTROL oldControlBits = 0;
         DWORD dwRevision = 0;

         if (!GetSecurityDescriptorControl(pFileSD, &oldControlBits,
            &dwRevision)) {
            _tprintf(TEXT("GetSecurityDescriptorControl() failed.")
                  TEXT("Error %d\n"), GetLastError());
            __leave;
         }

         if (oldControlBits & SE_DACL_AUTO_INHERITED) {
            controlBitsOfInterest =
               SE_DACL_AUTO_INHERIT_REQ |
               SE_DACL_AUTO_INHERITED;
            controlBitsToSet = controlBitsOfInterest;
         }
         else if (oldControlBits & SE_DACL_PROTECTED) {
            controlBitsOfInterest = SE_DACL_PROTECTED;
            controlBitsToSet = controlBitsOfInterest;
         }
         
         if (controlBitsOfInterest) {
            if (!_SetSecurityDescriptorControl(&newSD,
               controlBitsOfInterest,
               controlBitsToSet)) {
               _tprintf(TEXT("SetSecurityDescriptorControl() failed.")
                     TEXT("Error %d\n"), GetLastError());
               __leave;
            }
         }
      }

      // 
      // STEP 20: Set the new SD to the File.
      // 
      if (!SetFileSecurity(lpszFileName, secInfo,
            &newSD)) {
         _tprintf(TEXT("SetFileSecurity() failed. Error %d\n"), 
               GetLastError());
         __leave;
      }

      fResult = TRUE;

   } __finally {

      // 
      // STEP 21: Free allocated memory
      // 
      if (pUserSID)
         myheapfree(pUserSID);

      if (szDomain)
         myheapfree(szDomain);

      if (pFileSD)
         myheapfree(pFileSD);

      if (pNewACL)
         myheapfree(pNewACL);
   }
   
   return fResult;
}

int _tmain(int argc, TCHAR *argv[]) {

   if (argc < 3) {
      _tprintf(TEXT("usage: \"%s\" <FileName> <AccountName>\n"), argv[0]);
      return 1;
   }

   // argv[1] - FileName
   // argv[2] - Name of the User or Group account to add access
   if (!AddAccessRights(argv[1], argv[2], GENERIC_ALL)) {
      _tprintf(TEXT("AddAccessRights() failed.\n"));
      return 1;
   }
   else {
      _tprintf(TEXT("AddAccessRights() succeeded.\n"));
      return 0;
   }
}

				

Explanation of steps in sample code

  1. The LookupAccountName function is called to obtain the security identifier (SID) for the user or group name that is specified. The LookupAccountName function is actually called twice: once to determine the required buffer sizes, and then again to retrieve the account information. This SID obtained from this function is used later in a call to the AddAccessAllowedACE function. The LookupAccountName function also provides the domain where the user or group account is found.

    Note The LookupAccountName function can be a fairly expensive call, if multiple SAM databases need to be queried to retrieve the desired information. For more information about how to get the SID of the current user, click the following article number to view the article in the Microsoft Knowledge Base:

    111544 How to retrieve current user and domain names on Windows NT, Windows 2000, or Windows XP

    For more information about how to get the SID of well known or built in users or groups, click the following article number to view the article in the Microsoft Knowledge Base:

    157234 How to deal with localized and renamed user and group names

  2. The GetFileSecurity function is used to obtain a copy of the file's security descriptor (SD). The GetFileSecurity function is actually called twice: once to determine the required buffer size, and then again to retrieve the SD.

    Note Security descriptors have two possible formats: self-relative and absolute. The GetFileSecurity function returns an SD in self-relative format, but the SetFileSecurity function expects SD in absolute format. This is one reason that the code must create a new SD and copy the information, instead of simply modifying the SD from the GetFileSecurity function and passing it to the SetFileSecurity function. It is possible to call the MakeAbsoluteSD function to do the conversion, but there may not be enough room in the current ACL, as mentioned.
  3. Initialize a new SD by calling the InitializeSecurityDescriptor function.
  4. The GetSecurityDescriptorDacl function retrieves a pointer to the DACL in the SD.
  5. The GetAclInformation function is called to obtain size information on the file's DACL in the form of an ACL_SIZE_INFORMATION structure. This information is used when computing the size of the new DACL and when copying ACEs.
  6. Compute the exact number of bytes to allocate for the new DACL by using the information just obtained from the GetAclInformation function. The AclBytesInUse member of the ACL_SIZE_INFORMATION structure represents the number of bytes that are being used in the file's DACL. Add this number to the size of an ACCESS_ALLOWED_ACE and the size of the user's SID. Then subtract the size of a DWORD to obtain the exact number of bytes for the DACL.
  7. Allocate memory for the new ACL that ultimately contains the file's existing ACEs plus the access-allowed ACE.
  8. Initialize the ACL structure.
  9. Check the flag that is returned by the GetSecurityDescriptorDacl function to determine whether a DACL was present in the file's SD. If a DACL was not present, then skip the code that copies the file's ACEs to the new DACL. If a DACL is present, copy all the ACEs from the old DACL to the new DACL. The sample code assumes that the old DACL is already in Windows 2000 preferred order. To conform to the new Windows 2000 preferred order for the new DACL, first the code copies all non-inherited ACEs from the old DACL to the new DACL, irrespective of the ACE type. The AceCount member of the ACL_SIZE_INFORMATION structure contains the number of ACEs in the DACL. Loop once for each ACE.
  10. Retrieve a pointer to the ACE in the file's existing DACL.
  11. Check if the ACE obtained is a non-inherited ACE. Copy only non-inherited ACEs in this first for function loop. If the ACE is an inherited ACE, break from this for function loop, so that the new access allowed non-inherited ACE can be added in the correct position, immediately after all existing non-inherited ACEs.
  12. If the SID matches with the account specified, skip adding the ACE because we are adding an access allowed ACE with a different access mask.
  13. Copy the ACE to the new ACL. Pass MAXDWORD for the dwStartingAceIndex parameter of the AddAce function to ensure that the ACE is added to the end of the DACL. The AceSize member of the ACE_HEADER provides the size of the ACE.
  14. After copying all of the file's original non-inherited ACEs to the new ACL, add an access-allowed ACE, based on the value of dwAccessMask. The new ACE added here will be in the correct preferred order, immediately after all existing non-inherited ACEs.
  15. If an old DACL was present, copy the rest of the inherited ACEs from the old DACL to the new DACL, starting from the CurrentAceIndex. Loop once for each inherited ACE.
  16. Retrieve a pointer to the ACE in the file's existing DACL.
  17. Copy the ACE to the new ACL. Pass MAXDWORD for the dwStartingAceIndex parameter of the AddAce function to ensure that the ACE is added to the end of the DACL. The AceSize member of the ACE_HEADER provides the size of the ACE.
  18. Set the new DACL to the new SD by using the SetSecurityDescriptorDacl function.
  19. Copy the old security descriptor control flags regarding DACL automatic inheritance to the new security descriptor. This is done by using SetSecurityDescriptorControl function which is available only on Windows 2000 and above. The sample code calls the API at runtime, only if the API is available in advapi32.dll.
  20. Set the new SD by calling the SetFileSecurity function. The DACL_SECURITY_INFORMATION flag causes the DACL in the new SD to be applied to the file's SD. Only the file's DACL is set. The other security information in the file's SD remains unchanged.
  21. Free the memory that was allocated in the sample code.

Access-denied ACEs

Please note that a similar process can be used to add an access-denied ACE to a file's DACL. An access-denied ACE is used to deny a specific user or group access to a file. Access-denied ACEs should appear before access-allowed ACEs in an ACL. Therefore, when adapting the sample code to add an access-denied ACE, the call to the AddAccessDeniedAce function should precede the code that copies the existing ACEs to the new ACL.

Securing other objects

Access can be granted to objects other than files by substituting the GetFileSecurity function call with a GetKernelObjectSecurity, GetUserObjectSecurity, or GetPrivateObjectSecurity function call.

Modification Type:MajorLast Reviewed:7/22/2005
Keywords:kbACL kbAPI kbFAQ kbhowto kbKernBase KbSECTools kbSecurity KB102102