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
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.
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
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
if(!GetUserName(cCurrUser, &dwUserLen) ||
(NULL == (pSid = pSidGetOwnerSid(cCurrUser))))
MyHandleErrorCondition("Failed to get owner SID");
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;
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))
printf("\nFailed to get SID - %d", GetLastError());
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.
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
AccessMask, &pDacl) || !SetSecurityDescriptorSacl(&SecDesc,
TRUE, pDacl, FALSE))
MyHandleErrorCondition("Failed to create SACL");
The code to create a DACL is:
BOOL bConstructAclForObj(DWORD dwTrusteeCount,
DWORD dwLoop = 0;
PCHAR pLocalName = pTrusteeList;
if(NULL == (ppStoreSid = LocalAlloc(LMEM_ZEROINIT,
dwTrusteeCount * SIZEOF_POINTER)))
lpStoreSidBase = (LPVOID)ppStoreSid;
while(dwTrusteeCount > dwLoop)
if(NULL == (*ppStoreSid = pSidGetOwnerSid(pLocalName)))
printf("\nFailed to retrive trustee SID");
dwAclLen = GetLengthSid(ppStoreSid); +
sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD);
pLocalName += MAX_PATH;
dwAclLen += sizeof(ACCESS_ALLOWED_ACE);
ppStoreSid = lpStoreSidBase;
pRetAcl = LocalAlloc(LMEM_ZEROINIT, dwAclLen);
dwLoop = 0;
if(!pRetAcl || !InitializeAcl(pRetAcl,
while(dwTrusteeCount > dwLoop)
printf("\nFailed to add ACE to to ACL - %d", GetLastError());
*ppAcl = pRetAcl;
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.
MyHandleErrorCondition("The security descriptor"
" was not created properly");
hMutex = CreateMutex(NULL , TRUE, MY_MUTEX_NAME);
if(!hMutex || INVALID_HANDLE_VALUE == hMutex)
MyHandleErrorCondition("Failed to create mutex");
MyHandleErrorCondition("Failed to set security information");
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.