HOWTO: Add an Access-Allowed ACE to a File

ID: Q102102

The information in this article applies to:

SUMMARY

This article explains the process of adding an access-allowed (or access-denied) access control entry (ACE) to a file.

Adding an access-allowed ACE to a file's access control list (ACL) provides a means of granting or denying (using an access-denied ACE) access to the file to a particular user or group. In most cases, the file's ACL will not have enough free space to add an additional ACE, and therefore it is usually necessary to create a new ACL and copy the file's existing ACEs over to it. Once the ACEs are copied over and the access-allowed ACE is also added, the new ACL can be applied to the file's security descriptor (SD). This process is explained in detail in the section below. Sample code is provided at the end of this article.

Access can be granted or denied to objects other than files by substituting GetKernelObjectSecurity(), GetUserObjectSecurity() or GetPrivateObjectSecurity() for GetFileSecurity().

MORE INFORMATION

At the end of this article is sample code that defines a function named AddAccessRights(), which adds an access-allowed ACE to the specified file allowing the specified access. Steps 1-17 in the comments of the sample code are discussed in detail below:

1. GetUserName() is called to retrieve the name of the currently logged in

   user. The user name is stored in plszUserName[] array.

2. LookupAccountName() is called to obtain the SID of the user
   returned by GetUserName() in step 1. The resulting SID is stored
   in the UserSID variable and will be used later in the
   AddAccessAllowedACE() application programming interface (API)
   call. The LookupAccountName() API is also providing the user's
   domain in the plszDomain[] array. Please note that
   LookupAccountName() returns the SID of the first user or group
   that matches the name in plszUserName.

   NOTE: LookupAccountName can be a fairly expensive API because multiple
   SAM databases may need to be queried to retrieve the desired
   information. An alternative method to retrieve the SID is by looking
   in the process token. For additional information, please see the
   following article in the Microsoft Knowledge Base:

   ARTICLE-ID: Q111544
   TITLE     : Looking Up the Current User and Domain

3. GetFileSecurity() is used here to obtain a copy of the file's
   security descriptor (SD). The file's SD is placed into the ucSDbuf
   variable, which is declared a size of
   65536+SECURITY_DESCRIPTOR_MIN_LENGTH for simplicity. This value
   represents the maximum size of an SD, which ensures the SD will be
   of sufficient size.

4. Here we initialize the new security descriptor (NewSD variable) by
   calling the InitializeSecurityDescriptor() API. Because the
   SetFileSecurity() API requires that the security item being set is
   contained in a SD, we create and initialize NewSD.

5. Here GetSecurityDescriptorDacl() retrieves a pointer to the
   discretionary access control list (DACL) in the SD. The pointer is
   stored in the pACL variable.

6. GetAclInformation() is called here to obtain size information on
   the file's DACL in the form of a ACL_SIZE_INFORMATION structure.
   This information is used when computing the size of the new DACL
   and when copying ACEs.

7. This statement computes the exact number of bytes to allocate for
   the new DACL. The AclBytesInUse member represents the number of
   bytes being used in the file's DACL. We add this number to the
   size of an ACCESS_ALLOWED_ACE and the size of the user's SID.
   Subtracting the size of a DWORD is an adjustment required to
   obtain the exact number of bytes necessary.

8. Here we allocate memory for the new ACL that will ultimately
   contain the file's existing ACEs plus the access-allowed ACE.

9. In addition to allocating the memory, it is important to
   initialize the ACL structure as we do here.

10. Here we check the bDaclPresent flag returned by
   GetSecurityDescriptorDacl() to see if a DACL was present in the
   file's SD. If a DACL was not present, then we skip the code that
   copies the file's ACEs to the new DACL.

11. After verifying that there is at least one ACE in the file's DACL
   (by checking the AceCount member), we begin the loop to copy the
   individual ACEs to the new DACL.

12. Here we get a pointer to an ACE in the file's DACL by using the
   GetAce() API.

13. Now we add the ACE to the new DACL. It is important to note that
   we pass MAXDWORD for the dwStartingAceIndex parameter of AddAce()
   to ensure the ACE is added to the end of the DACL. The statement
   ((PACE_HEADER)pTempAce)->AceSize provides the size of the ACE.

14. Now that we have copied all the file's original ACEs over to our
   new DACL, we add the access-allowed ACE. The dwAccessMask variable
   will contain the access mask being granted. GENERIC_READ is an
   example of an access mask.

15. Because the SetFileSecurity() API can set a variety of security
   information, it takes a pointer to a security descriptor. For this
   reason, it is necessary to attach our new DACL to a temporary SD.
   This is done by using the SetSecurityDescriptorDacl() API.

16. Now that we have a SD containing the new DACL for the file, we set
   the DACL to the file's SD by calling SetFileSecurity(). The
   DACL_SECURITY_INFORMATION parameter indicates that we want the
   DACL in the provided SD applied to the file's SD. Please note that
   only the file's DACL is set, the other security information in the
   file's SD remains unchanged.

17. Here we free the memory that was allocated for the new DACL.

The following sample demonstrates the basic steps required to add an access- allowed ACE to a file's DACL. Please note that this same process can be used to add an access-denied ACE to a file's DACL. Because the access- denied ACE should appear before access-allowed ACEs, it is suggested that the call to AddAccessDeniedAce() precede the code that copies the existing ACEs to the new DACL.

Sample Code

   #define SD_SIZE (65536 + SECURITY_DESCRIPTOR_MIN_LENGTH)

   BOOL AddAccessRights(CHAR *pFileName, DWORD dwAcessMask)
   {
      // SID variables.

      UCHAR          psnuType[2048];
      UCHAR          lpszDomain[2048];
      DWORD          dwDomainLength = 250;
      UCHAR          UserSID[1024];
      DWORD          dwSIDBufSize=1024;

      // User name variables.

      UCHAR          lpszUserName[250];
      DWORD          dwUserNameLength = 250;

      // File SD variables.

      UCHAR          ucSDbuf[SD_SIZE];
      PSECURITY_DESCRIPTOR pFileSD=(PSECURITY_DESCRIPTOR)ucSDbuf;
      DWORD          dwSDLengthNeeded;

      // ACL variables.

      PACL           pACL;
      BOOL           bDaclPresent;
      BOOL           bDaclDefaulted;
      ACL_SIZE_INFORMATION AclInfo;

      // New ACL variables.

      PACL           pNewACL;
      DWORD          dwNewACLSize;

      // New SD variables.

      UCHAR                NewSD[SECURITY_DESCRIPTOR_MIN_LENGTH];
      PSECURITY_DESCRIPTOR psdNewSD=(PSECURITY_DESCRIPTOR)NewSD;

      // Temporary ACE.

      PVOID          pTempAce;
      UINT           CurrentAceIndex;

      // STEP 1: Get the logged on user name.

      if(!GetUserName(lpszUserName,&dwUserNameLength))
      {
         printf("Error %d:GetUserName\n",GetLastError());
         return(FALSE);
      }

      // STEP 2: Get SID for current user.

      if (!LookupAccountName((LPSTR) NULL,
          lpszUserName,
          UserSID,
          &dwSIDBufSize,
          lpszDomain,
          &dwDomainLength,
          (PSID_NAME_USE)psnuType))
      {
         printf("Error %d:LookupAccountName\n",GetLastError());
         return(FALSE);
      }

      // STEP 3: Get security descriptor (SD) for file.

      if(!GetFileSecurity(pFileName,
                    (SECURITY_INFORMATION)(DACL_SECURITY_INFORMATION),
                    pFileSD,
                    SD_SIZE,
                    (LPDWORD)&dwSDLengthNeeded))
      {
         printf("Error %d:GetFileSecurity\n",GetLastError());
         return(FALSE);
      }

      // STEP 4: Initialize new SD.

      if(!InitializeSecurityDescriptor
         (psdNewSD,SECURITY_DESCRIPTOR_REVISION))
      {
         printf("Error %d:InitializeSecurityDescriptor\n",GetLastError());
         return(FALSE);
      }

      // STEP 5: Get DACL from SD.

      if (!GetSecurityDescriptorDacl(pFileSD,
                       &bDaclPresent,
                       &pACL,
                       &bDaclDefaulted))
      {
         printf("Error %d:GetSecurityDescriptorDacl\n",GetLastError());
         return(FALSE);
      }

      // STEP 6: Get file ACL size information.

      if(!GetAclInformation(pACL,&AclInfo,sizeof(ACL_SIZE_INFORMATION),
         AclSizeInformation))
      {
         printf("Error %d:GetAclInformation\n",GetLastError());
         return(FALSE);
      }

      // STEP 7: Compute size needed for the new ACL.

      dwNewACLSize = AclInfo.AclBytesInUse +
                     sizeof(ACCESS_ALLOWED_ACE) +
                     GetLengthSid(UserSID) - sizeof(DWORD);

      // STEP 8: Allocate memory for new ACL.

      pNewACL = (PACL)LocalAlloc(LPTR, dwNewACLSize);

      // STEP 9: Initialize the new ACL.

      if(!InitializeAcl(pNewACL, dwNewACLSize, ACL_REVISION2))
      {
         printf("Error %d:InitializeAcl\n",GetLastError());
         LocalFree((HLOCAL) pNewACL);
         return(FALSE);
      }

      // STEP 10: If DACL is present, copy it to a new DACL.

      if(bDaclPresent)  // Only copy if DACL was present.
      {
         // STEP 11: Copy the file's ACEs to our new ACL.

         if(AclInfo.AceCount)
         {
            for(CurrentAceIndex = 0; CurrentAceIndex < AclInfo.AceCount;
               CurrentAceIndex++)
            {
               // STEP 12: Get an ACE.

               if(!GetAce(pACL,CurrentAceIndex,&pTempAce))
               {
                 printf("Error %d: GetAce\n",GetLastError());
                 LocalFree((HLOCAL) pNewACL);
                 return(FALSE);
               }

                // STEP 13: Add the ACE to the new ACL.

               if(!AddAce(pNewACL, ACL_REVISION, MAXDWORD, pTempAce,
                  ((PACE_HEADER)pTempAce)->AceSize))
               {
                  printf("Error %d:AddAce\n",GetLastError());
                  LocalFree((HLOCAL) pNewACL);
                  return(FALSE);
               }
             }
         }
      }

      // STEP 14: Add the access-allowed ACE to the new DACL.

      if(!AddAccessAllowedAce(pNewACL,ACL_REVISION2,dwAcessMask, &UserSID))
      {
         printf("Error %d:AddAccessAllowedAce",GetLastError());
         LocalFree((HLOCAL) pNewACL);
         return(FALSE);
      }

      // STEP 15: Set our new DACL to the file SD.

      if (!SetSecurityDescriptorDacl(psdNewSD,
                        TRUE,
                        pNewACL,
                        FALSE))
      {
         printf("Error %d:SetSecurityDescriptorDacl",GetLastError());
         LocalFree((HLOCAL) pNewACL);
         return(FALSE);
      }

      // STEP 16: Set the SD to the File.

      if (!SetFileSecurity(pFileName, DACL_SECURITY_INFORMATION,psdNewSD))
      {
         printf("Error %d:SetFileSecurity\n",GetLastError());
         LocalFree((HLOCAL) pNewACL);
         return(FALSE);
      }

      // STEP 17: Free the memory allocated for the new ACL.

      LocalFree((HLOCAL) pNewACL);
      return(TRUE);
   }

NOTE: Security descriptors have two possible formats: self-relative and absolute. GetFileSecurity() returns an SD in self-relative format, but SetFileSecurity() expects and absolute SD. This is one reason that the code must create a new SD and copy the information, instead of simply modifying the SD from GetFileSecurity() and passing it to SetFileSecurity(). It is possible to call MakeAbsoluteSD() to do the conversion, but there may not be enough room in the current ACL anyway, as mentioned above.

Additional query words: Keywords : kbAPI kbKernBase kbSecurity kbGrpKernBase

Last Reviewed: September 27, 1998