Click here to Skip to main content
15,867,568 members
Articles / Programming Languages / C++
Article

Securing NT objects

Rate me:
Please Sign up or sign in to vote.
3.76/5 (7 votes)
1 Aug 20057 min read 41.8K   650   18   4
Implementing discretionary access control on securable 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 objects 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 then the system assigns the process' 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 needs 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) which 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 retives 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 and ACEs:

After setting the primary owner and group of the object, the security descriptor's access control list needs to be set. ‘Access Control Lists’ (ACLs), as the name specifies, is a list of security information that manages access permissions to a securable object. ACLs contains 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 that permission for all kinds of access to an object is implicitly denied by the system. You explicitly need to add an ACCESS_ALLOWED ACE 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 messages 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 a DACL initialized with ACEs. When a securable object has no DACL (null DACL), no protection is assigned to the object. Hence all kinds of accesses to the object are 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 accesses to the object. Access permissions of all kinds, by default, are 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 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 the following 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 a Thread ‘A’ with two trustees FOO and BAR accessing two different Objects 'X' and 'Y' with two ACEs each as shown in the picture.

Sample image

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 the object ‘X’, the system reads the first ACE and cycles it with the threads trustee list. Since 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 fist 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, then the cycling stops and the thread is granted access to object ‘Y’.

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 using the AddAccessDeniedAce API and 'Access Allowed' ACEs using the AddAccessAllowedAce API. These APIs add the ACEs blindly to the end of the list. Hence it is the responsibility of the programmer 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 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 untill all trustess 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 allows ACE's to the ACL
        //Note that here we are using only access 
        //allowed ACE's. In case of explicitly
        //specifying Access deined ACE's you need 
        //to add ACE's as per canonical order.
        //The API's just add ace's to the end 
        //of the list and donot 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 using the SetSecurityDescriptorSacl API. SACLs do not play any role in moderating access permissions to an object. Their role is restricted to auditing of access to objects.

Securing the Object:

Once the security descriptor has been prepared, it can be verified 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 using the SetXXXXSecurity where XXX is the object type. To set the security for kernel objects, use the SetKernelObjectSecurity API, and to set the security for file objects, 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 in using the process' default security descriptor. Objects can also 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 have the need to synchronize access to objects which 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Generalsddl.h Pin
ravichavan22-Jun-10 23:54
ravichavan22-Jun-10 23:54 
GeneralGood Job!! Pin
pointer24-Jan-08 10:12
pointer24-Jan-08 10:12 
At Last i understood this beast!!
Your article is far more better then Richter and Clark book.
Thanx very much!!
GeneralGetting error 1336 ( Invalid Acl) Pin
Sharath C V3-Jan-07 18:22
professionalSharath C V3-Jan-07 18:22 
GeneralRe: Getting error 1336 ( Invalid Acl) Pin
lbeck377-Jun-07 5:42
lbeck377-Jun-07 5:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.