Export (0) Print
Expand All

Techniques for Securing Private Objects in Your Applications

 

Kenny Kerr
Software Consultant

March 2004

Applies to:
   Microsoft® Windows® security authorization
   C++ language

Summary: Want to know how you can extend the Windows operating system's rich security features to your own applications? Get the basics on Windows security authorization and creating your own security descriptors.

Download the associated SecuringPrivateObjects.exe sample code.


Contents

Introduction
Tokens, or Who Are You?
Security Descriptor Basics
Memory Management
Private Security Descriptors
Permissions
Access Control Lists
Access Control Editor
Conclusion

Introduction

Do you ever wonder how you can extend the Windows® operating system's rich security features to your own applications? Do you look at the Windows file system security editors and wish you could provide that level of security for your own business objects? In this article I start by explaining the basics of Windows security authorization. I then walk through manipulating security descriptors, how to create your own, and finally how to edit them in various ways. After reading this article you should have enough information to be able to apply these techniques in your own applications.

One of my aims in this article is to help make security programming practical and approachable. So I may not always go into exhaustive detail explaining a particular function. Rather, I will introduce a number of helper functions and classes that you can use to make your security code more robust and manageable. The helper functions and samples will not only show you how to use the various security functions but will make a point of showing you how they should be used safely and reliably in the face of exceptions and other error conditions.

Tokens, or Who Are You?

This article is all about managing access control, otherwise known as authorization. But before we can get to that, we need a way of identifying the user who is trying to access the secured resources. This is where tokens come in. Tokens represent logon sessions on a computer. A logon session is created whenever a user accesses a computer interactively or remotely. A token is a handle that can be used to query or manipulate the logon session. After you have a token, you can learn who the user is that the logon session represents and determine whether he or she should be granted access to the secured resources.

Because all applications run in the context of a logon session, there is always some sort of token available to indicate who the user is. What makes this a little confusing is that there may be one or more different tokens, or security contexts, present at any given time. Each may be representing a different user. At the very least, there will be a token attached to the process. To get this token, you can use the OpenProcessToken function.

CHandle token;
Helpers::CheckError(::OpenProcessToken(::GetCurrentProcess(),
                                       TOKEN_QUERY,
                                       &token.m_h));

CHandle is a wrapper class provided by the Active Template Library (ATL) that ensures that the handle is closed by calling the CloseHandle function when the token goes out of scope. CheckError is a helper function in my Helpers namespace. CheckError throws an HRESULT describing the error that occurred. With all the different ways an error can be represented in Windows C programming, I like to standardize on HRESULTs for consistency. The Helpers namespace is available with the download for this article. The GetCurrentProcess function returns a pseudo-handle that represents the current process. Because this is not a real handle, the HANDLE returned does not need to be freed by using the CloseHandle function.

Another token that may be available is the thread token. The thread token can be retrieved by using the OpenThreadToken function.

CHandle token;
Helpers::CheckError(::OpenThreadToken(::GetCurrentThread(),
                                      TOKEN_QUERY,
                                      TRUE, // open as self
                                      &token.m_h));

As with GetCurrentProcess, GetCurrentThread returns a pseudo-handle, so there's no need to call CloseHandle for it. Unlike OpenProcessToken, OpenThreadToken may fail with ERROR_NO_TOKEN if there is no token associated with the current thread.

In some cases there may even be a third token that represents yet another security context. For example, ASP.NET lets you turn off impersonation of the client, in which case you can get the identity of the client explicitly by using the HttpContext class.

Now that we have a token, it would help if we could do something interesting with it. The simplest thing you can do with a token is to query it for information about the logon session. To do this you can use the GetTokenInformation function. Because GetTokenInformation can be used to query for different classes of information and is rather tricky to use, I wrote a wrapper function template to make it less error-prone. The following example illustrates how you can query the token for its user information.

CLocalMemoryT<PTOKEN_USER> tokenUser(Helpers::GetTokenInformation<TOKEN_USER>(token,
                                                                              TokenUser));
CComBSTR string = Helpers::ConvertSidToStringSid(tokenUser->User.Sid);

The ConvertSidToStringSid helper function converts the binary security identifier (SID) to a human-readable string. SIDs are used to represent user accounts in a machine-readable format. If you are interested in just what my wrapper functions do, download the source code for this article and take a look. The CLocalMemoryT class template will be discussed later when I cover memory management.

Security Descriptor Basics

Now that we know how to identify the user, we need a way to manage the different permissions that various users will have. This is where security descriptors come in. A security descriptor contains a number of different pieces of information. The most interesting of these are the owner security identifier (SID) and the two access control lists (ACLs). The owner SID identifies the user who owns the object. The two ACLs are the discretionary ACL and the system ACL. Because the system ACL really has nothing to do with access control, this article focuses on the discretionary ACL (DACL).

Security descriptors are represented by the SECURITY_DESCRIPTOR structure. You should avoid manipulating this structure directly. Microsoft provides a number of handy functions for querying and manipulating security descriptors for built-in objects like files and registry keys. The following example illustrates how to get the owner SID and DACL for the Program Files directory on my computer.

CLocalMemory securityDescriptor;
PSID sid = 0;
PACL dacl = 0;

Helpers::CheckError(::GetNamedSecurityInfo(_T("C:\\Program Files"),
                                           SE_FILE_OBJECT,
                                           OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
                                           &sid,
                                           0, // group
                                           &dacl,
                                           0, // sacl
                                           &securityDescriptor.m_ptr));

GetNamedSecurityInfo is a versatile function that allows you to query most, if not all, of the built-in secure objects. The first parameter is the name, or path, of the object you would like to query. The second parameter indicates the type of object. In this case I am querying a file system object. You could change this to SE_REGISTRY_KEY to query a registry object, for example. Next, you indicate the information you are interested in by using the SECURITY_INFORMATION enumeration. The following four parameters are pointers to the four main parts of the security descriptor. Fortunately, you can pass 0 for the parts that you are not interested in. Finally, the last parameter is a pointer to the security descriptor itself. This is actually a copy of the security descriptor that you must free by using the LocalFree function.

There is also a complimentary function named SetNamedSecurityInfo to update a built-in object's security descriptor. It works in much the same way, so I won't go into detail about it here.

Memory Management

Before we move on, I think it is important that we talk about memory management. Memory management, and resource management in general, is an important aspect of writing secure and robust code. The best way to write memory management code is not to write any at all. You first need to understand the memory management techniques used by the various functions you are dealing with, then you need to wrap it up in a class or two to ensure proper clean-up at the appropriate time. If you don't do this, your code will leak resources, or worse, introduce bugs that could allow bad guys access to resources you want to keep secure.

So far I have introduced the CLocalMemoryT class template without really explaining what it's for. Most of the security functions related to security descriptors make use of local memory. Local memory has its root in 16-bit Windows, where memory management was considerably more complex. The local memory functions, such as LocalAlloc and LocalFree, are available today mostly for backward compatibility with 16-bit applications and older API functions that rely on them as part of their semantics.

To make dealing with local memory easier, I wrote a simple class that wraps a local memory pointer. You can think of CLocalMemoryT as a smart pointer class because I overloaded the member selection operator (operator ->). This is possible because it is a class template, and you can indicate the type, or structure, of the memory pointed to through the template parameter. You can use CLocalMemoryT to create a new local memory block, but typically you will use it to attach to an existing memory block returned by a function. The destructor will then free the memory by calling LocalFree. Some of the data structures used by the security functions are opaque. To make them easier to use, I have defined the following type definition at the bottom of the CLocalMemory header file.

typedef CLocalMemoryT<PVOID> CLocalMemory;

This is useful for managing the memory for a SECURITY_DESCRIPTOR object, for example.

Private Security Descriptors

GetNamedSecurityInfo and SetNamedSecurityInfo are great for built-in objects. But what about private objects, such as those found in my own application's business logic? Can these functions be extended to support private objects? Unfortunately, the answer is no. Because each resource, such as the file system or the registry, defines its own way of persisting security descriptors, there is no way for these functions to possibly know how to query or set the security information for private objects that you create. Fortunately, there is a solution.

First, we need a way to create private security descriptors. You can create your own security descriptor from scratch by using the CreatePrivateObjectSecurityEx function. This is a rather versatile function with two main uses: to create new security descriptors and to update the inheritance of existing security descriptors. The function is prototyped as follows.

BOOL CreatePrivateObjectSecurityEx(PSECURITY_DESCRIPTOR parentDescriptor,
                                   PSECURITY_DESCRIPTOR defaultDescriptor,
                                   PSECURITY_DESCRIPTOR* newDescriptor,
                                   GUID* type,
                                   BOOL isContainer,
                                   ULONG autoInheritFlags,
                                   HANDLE token,
                                   PGENERIC_MAPPING genericMapping);

The parentDescriptor parameter is used to indicate the parent object whose ACL should be inherited. If the object does not have a parent, pass zero. The main purpose of the defaultDescriptor parameter is updating an existing security descriptor with inheritable access control entries (ACEs) after parentDescriptor has changed. The way to do this however is to create a new security descriptor that will be returned by the newDescriptor parameter, and then free the original. To carry over explicit entries for the object's ACL, pass the existing security descriptor as the defaultDescriptor parameter.

When dealing with Active Directory object security, type is used. To indicate whether the object that the security descriptor represents is a container for other secure objects, isContainer is used. To affect how different inheritable ACEs flow to the newly created security descriptor, autoInheritFlags is used. You can simply pass SEF_DACL_AUTO_INHERIT to inherit any inheritable ACEs. However, when you are reflowing the inheritable ACEs from a parent security descriptor, you should also include the SEF_AVOID_PRIVILEGE_CHECK and SEF_AVOID_OWNER_CHECK flags to avoid access checks against the user, which are unnecessary when only updating the inheritance. For more information about managing ACL inheritance, see Keith Brown's book Programming Windows Security.

To identify the user on whose behalf the object is being created, token is used to retrieve default values for the new security descriptor, such as the owner SID. The fact that the user token is passed explicitly is convenient for server applications because impersonation is not required.

Finally, genericMapping is used to provide information about how explicit, object-specific permissions map to the four generic permissions: read, write, execute, and all. Permissions are discussed in the next section.

After you have finished using the security descriptor, you must free it by calling DestroyPrivateObjectSecurity.

Now that we can create our own security descriptors, we need a way to query and modify them. Although GetNamedSecurityInfo and SetNamedSecurityInfo are no good for private objects, there are functions available for this purpose. To modify a private security descriptor you need to use the SetPrivateObjectSecurityEx function. The function is prototyped as follows.

BOOL SetPrivateObjectSecurityEx(SECURITY_INFORMATION securityInformation,
                                PSECURITY_DESCRIPTOR modificationDescriptor,
                                PSECURITY_DESCRIPTOR* securityDescriptor,
                                ULONG autoInheritFlags,
                                PGENERIC_MAPPING genericMapping,
                                HANDLE token);

SetPrivateObjectSecurityEx calls LocalReAlloc, if necessary, to allocate enough memory for the new information being set. This is why a pointer to a pointer to the security descriptor is required; after calling SetPrivateObjectSecurityEx, the memory location pointed to by securityDescriptor will likely have changed.

As you can see, SetPrivateObjectSecurityEx does not provide individual parameters, like SetNamedSecurityInfo does, for setting individual parts of the security descriptor. SetPrivateObjectSecurityEx requires that you provide an existing security descriptor to copy values from. Fortunately, it's pretty easy to build a security descriptor object on the stack and prepare it with the information you would like to copy into your private security descriptor. Here's an example.

CWellKnownSid adminSid = CWellKnownSid::Administrators();

SECURITY_DESCRIPTOR templateDescriptor = { 0 };

Helpers::CheckError(::InitializeSecurityDescriptor(&templateDescriptor,
                                                   SECURITY_DESCRIPTOR_REVISION));

Helpers::CheckError(::SetSecurityDescriptorOwner(&templateDescriptor,
                                                 &adminSid,
                                                 false));

Helpers::CheckError(::SetPrivateObjectSecurityEx(OWNER_SECURITY_INFORMATION,
                                                 &templateDescriptor,
                                                 &securityDescriptor,
                                                 SEF_AVOID_PRIVILEGE_CHECK,
                                                 &genericMapping,
                                                 0));

The templateDescriptor is a stack-based security descriptor. Be sure to zero out the memory before moving on. InitializeSecurityDescriptor sets the revision level, but otherwise leaves the security descriptor empty. SetSecurityDescriptorOwner sets the owner SID to the well-known local Administrators group. This is aided by my CWellKnownSid class, which you can get from the download for this article. Finally, a call is made to SetPrivateObjectSecurityEx to copy the owner information from our template descriptor to the private security descriptor contained in securityDescriptor.

You may be wondering why you can't just use these functions to set the parts of the private security descriptor directly. There are two different memory formats for security descriptors. An absolute security descriptor contains pointers to the security information it contains. This memory is allocated separately from the memory for the security descriptor structure. A self-relative security descriptor, on the other hand, stores all its information in a contiguous block of memory. Instead of storing pointers, the security descriptor instead stores offsets into memory.

With an understanding of the different ways in which security descriptors can be represented in memory, things should start to fall into place. Private security descriptors are always self-relative. That is why the functions for dealing with them are more complex. Creating the security descriptor on the stack was easy because it was an absolute security descriptor, and functions like SetSecurityDescriptorOwner merely have to set a pointer value in the stack-based security descriptor.

Fortunately, querying a private security descriptor is more straightforward. You can use standard functions like GetSecurityDescriptorOwner and GetSecurityDescriptorDacl to get the different parts. There is also a function named GetPrivateObjectSecurity, but it's not much use for interrogating a security descriptor. It comes in handy when working with the access control editor, which we'll cover later in this article.

Permissions

I've been putting off talking about permissions, otherwise known as access rights. It's about as exciting as talking about memory management. Nevertheless it is vital to understand how to design the permissions for your private objects. Permissions are used every time you make a call to a function that may provide access to a secure resource. For example, the well-known CreateFile function has a parameter that takes an access bitmask, where each bit may represent a specific permission.

Just as the file system defines specific permissions, such as FILE_APPEND_DATA and FILE_TRAVERSE, you must also define specific permissions for your own objects. Of the 32-bit access mask, 16 bits are available for specific permissions. After you've defined any specific permissions for your objects, you need to categorize them into four generic permissions: GENERIC_READ, GENERIC_WRITE, GENERIC_EXECUTE, or GENERIC_ALL. In this way, a programmer can simply state that read access is required and the permissions that logically map to read access will be applied. But because none of the security functions know how the specific permissions map to the generic permissions, you need to fill in a GENERIC_MAPPING structure that is then passed to many of the security functions.

So, with a basic understanding of permissions we could define the following permissions for a particular widget resource.

namespace Permissions
{
    const DWORD AddWidget       = 0x0001;
    const DWORD ListWidgets     = 0x0002;
    const DWORD RenameWidget    = 0x0004;
    const DWORD ReadWidgetData  = 0x0008;
    const DWORD WriteWidgetData = 0x0010;

    const DWORD GenericRead     = STANDARD_RIGHTS_READ |
                                  ListWidgets |
                                  ReadWidgetData;

    const DWORD GenericWrite    = STANDARD_RIGHTS_WRITE |
                                  AddWidget |
                                  RenameWidget | 
                                  WriteWidgetData;

    const DWORD GenericExecute  = STANDARD_RIGHTS_EXECUTE;

    const DWORD GenericAll      = STANDARD_RIGHTS_REQUIRED |
                                  GenericRead |
                                  GenericWrite |
                                  GenericExecute;
}

At this point you should be able to populate a global GENERIC_MAPPING structure and pass a pointer to it to all the functions that require it. Now a programmer can simply use a generic permission, such as GENERIC_WRITE and it will map to Permissions::GenericRead for our widget. A careful programmer might not assume that the user will have all the permissions. In this case, he or she could use one of the specific permissions such as Permissions::RenameWidget. Of course, it is still possible to use one of the standard permissions such as DELETE, assuming we provide a specific meaning to it for our widget.

Access Control Lists

The DACL is the heart of the security descriptor. Each access control entry (ACE) in the list defines the permissions that a particular user or group has on the resource. An ACE can be either positive or negative. A positive ACE indicates that the user or group is granted the particular permissions, and a negative ACE indicates that the permissions are to be denied. If a user is not present in the ACL, either directly or as a member of a group, he or she is denied any access.

An ACL is stored as a contiguous block of memory that consists of an ACL structure followed by an ordered list of ACEs. The following example demonstrates how to build a simple ACL.

CLocalMemoryT<PACL> acl(100);

Helpers::CheckError(::InitializeAcl(acl.m_ptr,
                                    100,
                                    ACL_REVISION));

DWORD inheritFlags = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE;

CWellKnownSid everyoneSid = CWellKnownSid::Everyone();

Helpers::CheckError(::AddAccessAllowedAceEx(acl.m_ptr,
                                            ACL_REVISION,
                                            inheritFlags,
                                            GENERIC_READ,
                                            &everyoneSid));

CLocalMemoryT<PSID> userSid(Helpers::GetUserSid(token));

Helpers::CheckError(::AddAccessAllowedAceEx(acl.m_ptr,
                                            ACL_REVISION,
                                            inheritFlags,
                                            GENERIC_ALL,
                                            userSid.m_ptr));

Because ACLs are stored in contiguous memory, I need to allocate a block of memory big enough to hold the ACL header and all of its entries. It's not too important how much memory you allocate, just as long as it's enough to hold all the entries. You typically follow the creation of an ACL with a call to a resource manager that will create a copy of it anyway. I use the AddAccessAllowedAceEx function to grant everyone read permissions and the user represented by the token all permissions.

Creating and editing more realistic and complex ACLs directly can be very difficult and error prone. The main reason is that the order of the ACEs in the list matter because the list is traversed from top to bottom when performing an access check. If you add a negative ACE to the bottom of the list, and the user is allowed access by an ACE at the top of the list, the access check will succeed even though the user has been denied access. The access check performs this short-circuit technique for efficiency. As long as you order the ACEs correctly, all will be well. The best advice I can give is to avoid manipulating ACLs directly at all. The next section will tell you how.

Access Control Editor

The access control editor used by the Windows shell is available for programmers to use in their own applications (see Figure 1). The access control editor is an extremely powerful and flexible editor for manipulating all the different parts of a security descriptor. The editor is exposed through two deceptively simple functions. CreateSecurityPage creates the familiar security property page that you can add to your own property sheet. EditSecurity is a helper function that slaps the security page in a property sheet and displays it for you. It seems simple enough, but there is a catch. Both require you to implement a rather unorthodox COM interface called ISecurityInformation. It's unorthodox because it doesn't follow standard COM memory management rules. Rather, it opts to continue using the memory management functions and techniques used by much of the security functions, relying on global memory and LocalAlloc. This also makes it extremely hard to implement in C#, or any other managed language. Just think of ISecurityInformation as a glorified C callback mechanism.

ms995350.secprivateobjects_01(en-us,MSDN.10).gif

Figure 1. Security properties page in the access control editor

The GetObjectInformation method allows you to indicate how you would like to customize the access control editor. For example, you can use this to show or hide the advanced button, which provides a more advanced security descriptor editor than the standard property page (see Figure 2). Other options that can be controlled include whether to allow the user to view and change the security descriptor's owner SID and system ACL.

ms995350.secprivateobjects_02(en-us,MSDN.10).gif

Figure 2. Advanced security settings dialog box

The GetSecurity method is called at various times when the editor needs to populate various controls with information about the security descriptor. The GetPrivateObjectSecurity function, which I mentioned earlier in this article, now comes in very handy. The GetSecurity method needs to return a copy of part of the security descriptor being edited. This is exactly what GetPrivateObjectSecurity does.

The SetSecurity method is called after the user has made changes in the editor that need to be saved. Implementing SetSecurity is a snap with the help of the SetPrivateObjectSecurityEx function, which I discussed previously.

The GetAccessRights method is called to get a list of specific and general permissions for the type of object being edited. The implementation involves creating an array of SI_ACCESS structures. Each SI_ACCESS structure identifies a specific or general permission, along with its bitmask bits, friendly name, and other flags. Because this information describes all instances of a security descriptor, the array is typically declared static.

Of the remaining methods, the only one of real interest is GetInheritTypes. It is called to allow the editor to determine how to attribute ACEs for inheritance. You should create a static array of SI_INHERIT_TYPE structures to describe the different combinations of inheritance flags to provide. Typically an object might provide the following options if it supported inheritance.

Applies toFlags
Object and childrenCONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE
Children onlyCONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE | INHERIT_ONLY_ACE
Object only0

Implementing ISecurityInformation can be quite daunting at fist, but with a bit of practice and some good examples to look at you'll soon be well on your way. The download for this article contains a number of useful helper functions, as well my CSecurityDescriptor class that helps with creating, editing, and managing ACL inheritance. The download also contains a sample implementation of ISecurityInformation, and the C++ project provides a simple example of using these classes.

Conclusion

Extending the Windows security model to private objects requires a good understanding of security descriptors in general and the functions related to managing private security descriptors in particular. Using the Windows access control editor you can build rich, security-aware applications. You should now be ready to experiment with security descriptors for yourself and consider employing the techniques described here for object-centric access control in your own applications.

Kenny Kerr spends most of his time designing and building distributed applications for the Microsoft Windows platform. He also has a particular passion for security programming. Reach Kenny at kennykerr@hotmail.com or visit his website: http://www.kennyandkarin.com/Kenny/.

Show:
© 2014 Microsoft