Securing NT Objects

Abstract

Windows provides a rich set of security features to secure its objects. Because of the vastness of the security features, its complicated documentation and contrastingly fewer examples, it's still a poorly understood concept. This article is an attempt to describe the basic details of securing NT objects and the Win32 APIs required. Refer to the Microsoft developer network documentation for an exhaustive overview.

The article assumes that the reader has a basic idea of NT objects and the security subsystem. The accompanying source code is not complete by itself. It just describes the sequence of steps to secure an object. The source code needs to be modified as per the user's requirement.

Securing an Object Using the NT Security Model

Security Descriptors

The security information associated with an object is defined by the SECURITY_DESCRIPTOR structure. This is a variable length structure. Four prime components of the structure that are worth attention are the Owner SID, the Group SID, the Discretionary ACL (DACL), and the System ACL (SACL).

The security descriptor should be initialized using the InitializeSecurityDescriptor API. If a NULL security is specified, the system assigns the processs default security descriptor to the object.

SECURITY_DESCRIPTOR    SecDesc;

//Initilaize the security descriptor structure
if(!InitializeSecurityDescriptor(&SecDesc,
                                 SECURITY_DESCRIPTOR_REVISION))
    MyHandleErrorCondition("Failed to initialize security
                            descriptor");

After initializing the SD (SECURITY_DESCRIPTOR), the Owner ID, Group ID, DACL, and SACL need to be filled. The owner SID and the Group SID contain information about the primary owner and group for the particular object. If these parameters are NULL, the object is not owned by anyone. The owner and group SID can be set by the SetSecurityDescriptorOwner and SetSecurityDescriptorGroup APIs. The security identifier (SID) of the account(owner/group) that is to be set as the primary owner/group of the object needs to be passed as a parameter to these APIs. The SID for the account can be obtained by the LookupAccountName API.

/*
GetUserName API retrives the current user name whose SID is to be
retrieved. To obtain the SID for different user, pass the username
as a parameter to the pSidGetOwnerSid function which retrives SID
for a given account name
*/
if(!GetUserName(cCurrUser, &dwUserLen) ||
   (NULL == (pSid = pSidGetOwnerSid(cCurrUser))))
   MyHandleErrorCondition("Failed to get owner SID");

//Set the object owner in the security descriptor
if(!SetSecurityDescriptorOwner(&SecDesc, pSid, FALSE))
   MyHandleErrorCondition("Failed to set security descriptor owner");

The user SID is obtained as follows

PSID
pSidGetOwnerSid(LPSTR    lpUser)
{
   DWORD        dwSidLen = 0, dwDomainLen = 0;
   SID_NAME_USE    SidNameUse;

   //The function on the first call retrieves the length that we
   //need to initialize the SID & domain name pointers
   if(!LookupAccountName(NULL, lpUser, NULL, &dwSidLen,
                         NULL, &dwDomainLen, &SidNameUse))
   {
      if(ERROR_INSUFFICIENT_BUFFER == GetLastError())
      {
         PSID     pSid = LocalAlloc(LMEM_ZEROINIT, dwSidLen);
         LPSTR    lpDomainName = LocalAlloc(LMEM_ZEROINIT,
                                            dwDomainLen);

         if(pSid && lpDomainName &&
            LookupAccountName(NULL, lpUser, pSid, &dwSidLen,
            lpDomainName, &dwDomainLen, &SidNameUse))
            return pSid;
      }
   }

    printf("\nFailed to get SID - %d", GetLastError());

    return NULL;    //Was not able to retrive PSID
}

The ACLs & ACEs

After setting the primary owner and group of the object, the security descriptor's access control list needs to be set. An Access Control List (ACL), as the name specifies, is a list of security information that manages access permissions to a securable object. ACLs contain a list of entries known as Access Control Entries (ACEs). Each ACE defines a set of access permissions for accounts in the system. An ACE can be defined as a container that contains the permissions for a particular trustee (group/user account) and the trustee's identification in the form of security identifier (SID).

Note: Permission for all kinds of access to an object is implicitly denied by the system. You need to add an ACCESS_ALLOWED ACE explicitly to the ACL for the requested access to be granted.

The two types of ACLs are the Discretionary ACL (DACL) or the System ACL (SACL). Discretionary ACL (DACL) defines the access permissions to the particular object and the System ACL (SACL) defines the generation of audit message on attempts made over securable objects.

Discretionary ACL (DACL)

When a thread tries to access a securable object, the decision to grant the requested access is taken on the basis of the object's DACL. A security descriptor can have a null DACL, an empty DACL, or DACL initialized with ACEs. When a securable object has no DACL (null DACL), no protection is assigned to the object. Hence, all kinds of access to the object is possible, thereby rendering the object insecure. Contrastingly, an empty DACL (an initialized DACL with no ACE) denies any access to the object. This is in keeping with the rule that you need to explicitly set permissions for all kinds of access to the object. Access permission of all kinds, by default, is denied implicitly. If a thread accesses a secured object, the system goes through the DACL list for the particular object comparing the access permission for each trustee of an ACE with the trustee list of the thread that accessed the object. The system examines the entire list of ACEs in sequence until one of the following events occurs.

  • An access-denied ACE denies any of the requested permissions to one of the trustees.
  • An access-allowed ACE allows all requested permissions explicitly to one of the trustees.
  • In case all ACEs were checked and still a requested permission(s) is pending for whom no explicit ACE was specified, the permission for the particular access is denied implicitly. The order of setting ACEs should follow the canonical order. In general, as specified in MSDN, the ACE should follow this hierarchy:

    • All explicit ACEs are placed in a group before any inherited ACEs.
    • Within the group of explicit ACEs, access-denied ACEs are placed before access-allowed ACEs.
    • Inherited ACEs are placed in the order in which they are inherited. ACEs inherited from the child object's parent come first, and then ACEs inherited from the grandparent, and so on up the tree of objects.
    • For each level of inherited ACEs, access-denied ACEs are placed before access-allowed ACEs.

Any change in the order will not result in the desired security effect expected by the user. For example, consider Thread A with two trustees, FOO and BAR, accesses two different Objects, X and Y, with two ACEs each, as shown in the following picture.

Theoretically speaking, the rules mean the same for both objects: Deny access for trustee FOO and allow access for trustee BAR. But, when the thread accesses Object X, the system reads the first ACE and cycles it with the thread's trustee list. Because FOO is denied access, the comparison stops there and the thread is denied access to the object. But, when the thread accesses Object Y, the system reads the first ACE of Object Y and cycles it through the thread's trustee list. It finds that the trustee BAR is allowed access. If all the permissions requested are satisfied for this trustee, the cycling stops and the thread is granted access to Object Y.

Securing NT Objects

To set a DACL in a SD, a Discretionary ACL needs to be initialized using the InitializeAcl API. Then, Access Denied ACEs are to be added first, followed by the Access Allowed ACEs. Access Denied ACEs can be added by using the AddAccessDeniedAce API and Access Allowed ACEs by using the AddAccessAllowedAce API. These APIs add the ACEs blindly to the end of the list. Hence, it is the programmer's responsibility to follow the canonical order in adding the ACEs. Once the ACEs are added to the DACL, it can be set to the security descriptor by using the SetSecurityDescriptorDacl API.

//Construct the DACL for the object
//The same function can be used to construc SACL by passing valid
//parameters
if(!bConstructAclForObj(MY_TRUSTEE_COUNT, pTrusteeArray,
                        AccessMask, &pDacl) ||
                        !SetSecurityDescriptorSacl(&SecDesc,
                        TRUE, pDacl, FALSE))
   MyHandleErrorCondition("Failed to create SACL");

The code to create a DACL is:

BOOL
bConstructAclForObj(     DWORD    dwTrusteeCount,
                         PCHAR    pTrusteeList,
                         PDWORD   pDwMaskList,
                         PACL     *ppAcl)
{
   DWORD     dwLoop = 0;
   DWORD     dwAclLen;
   PACL      pRetAcl;
   PSID      *ppStoreSid;
   LPVOID    lpStoreSidBase;
   PCHAR     pLocalName = pTrusteeList;


   if(!ppAcl)
        return FALSE;

   if(NULL == (ppStoreSid = LocalAlloc(LMEM_ZEROINIT,
               dwTrusteeCount * SIZEOF_POINTER)))
      return FALSE;

   lpStoreSidBase = (LPVOID)ppStoreSid;    //Save the initial
                                           //pointer since we
                                           //loop until all
                                           //trusteess are worked
                                           //with
   while(dwTrusteeCount > dwLoop)
   {
      if(NULL == (*ppStoreSid = pSidGetOwnerSid(pLocalName)))
      {
         printf("\nFailed to retrive trustee SID");
         return FALSE;
      }

      dwAclLen = GetLengthSid(ppStoreSid);
               + sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD);
      ppStoreSid ++;
      pLocalName += MAX_PATH;
      dwLoop++;
   }

   dwAclLen += sizeof(ACCESS_ALLOWED_ACE);
   ppStoreSid = lpStoreSidBase;
   pRetAcl = LocalAlloc(LMEM_ZEROINIT, dwAclLen);
   dwLoop = 0;

   //Initialize ACL
   if(!pRetAcl || !InitializeAcl(pRetAcl, dwAclLen, ACL_REVISION))
      return FALSE;

   while(dwTrusteeCount > dwLoop)
   {
      //Add the access-allowed ACEs to the ACL.
      //Note that here we are using only access-allowed ACEs.
      //In case of explicitly specifying Access deined ACEs, you
      //need to add ACEs as per canonical order.
      //The APIs just add access to the end of the list and do not
      //follow cannonical order. We need to follow it explicitly.
      if(!AddAccessAllowedAce(pRetAcl, ACL_REVISION,
         pDwMaskList[dwLoop], ppStoreSid[dwLoop]))
      {
         printf("\nFailed to add ACE to to ACL - %d",
                GetLastError());
         return FALSE;
      }

        dwLoop++;
   }

   *ppAcl = pRetAcl;

   return TRUE;
}

The construction of SACLs is quiet similar to the DACLs. Once a System ACL is constructed, it needs to be set in the security descriptor if appropriate. This can be done by using the SetSecurityDescriptorSacl API. SACLs do not play any role in moderating access permissions to an object. Their role is restricted to auditing access to objects.

Securing the object

Once the security descriptor has been prepared, it can be verified by using the IsValidSecurityDescriptor API. This API returns a Boolean value indicating the validity of the security descriptor. If the security descriptor is valid, it can be set to the respective object by using the SetXXXXSecurity where XXX is the object type. To set the security for kernel objects, use the SetKernelObjectSecurity API, and to set security for a file object, use the SerFileSecurity API. Refer to MSDN for the classification of objects and the respective APIs.

//Check the validity of the security descriptor created
if(!IsValidSecurityDescriptor(&SecDesc))
   MyHandleErrorCondition("The security descriptor was not
                           created properly");

//Create mutex
hMutex = CreateMutex(NULL , TRUE, MY_MUTEX_NAME);

if(!hMutex || INVALID_HANDLE_VALUE == hMutex)
   MyHandleErrorCondition("Failed to create mutex");

//Set the SD to the mutex object
if(!SetKernelObjectSecurity(hMutex,
   DACL_SECURITY_INFORMATION|OWNER_SECURITY_INFORMATION, &SecDesc))
   MyHandleErrorCondition("Failed to set security information");

Conclusion

Most programmers get over the hardship of creating security descriptors by simply passing a NULL security descriptor. This allows the system to use the processs default security descriptor. Objects also can be made secure by using unnamed objects. Unnamed objects cannot be manipulated by any third-party application. But, we cannot get away with such practices all the time. Client/Server model programs working in a multi-user environment generally need to synchronize access to objects; this makes the naming of these objects essential. Such situations call for a need to secure these objects. The source code that accompanies this article is just an attempt to explain the sequence of steps that go in making an object secure. It is not complete by any means. The source code needs to be tailored as per the user's requirements.



About the Author

Srikanth Srinivasan

Presently am working with the bug daddy of software industry. Most of my work is restricted to middleware applications and I dont fiddle much with the GUI layer. My programming interests are the NT kernel, file system drivers and NT system programming. Given my work profile, I have to work with quiet a few UNIX platforms but my personal taste is restricted to the NT domain.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: November 20, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Are you wanting to target two or more platforms such as iOS, Android, and/or Windows? You are not alone. 90% of enterprises today are targeting two or more platforms. Attend this eSeminar to discover how mobile app developers can rely on one IDE to create applications across platforms and approaches (web, native, and/or hybrid), saving time, money, and effort and introducing apps to market faster. You'll learn the trade-offs for gaining long …

  • IBM Worklight is a mobile application development platform that lets you extend your business to mobile devices. It is designed to provide an open, comprehensive platform to build, run and manage HTML5, hybrid and native mobile apps.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds