Many programs need to be able to store sensitive information in the file system or the registry. To protect this information, you need to properly define an ACL, because only you know who needs access to your information. In this article and my next one, I’ll explain how to create and apply an ACL, and present some background on how the Windows 2000 and Windows NT security subsystem works.

Within Win2K and NT, nearly everything is an object (although not in the strict C++ sense of the word). Some objects are containers that might have other objects associated with them. One example of a container object is a directory, which can contain files and possibly other directories. Similarly, a registry key is an object that contains values and possibly other registry keys. So, if an ACL applies to a directory, for example, flags determine which portions of the ACL apply to newly created directories and files and which apply to the directory itself. This process is known as inheritance. Although many configurations are possible, in practice newly created containers typically inherit the parent permissions.

If your information is the least bit sensitive, the inherited permissions typically won’t be appropriate for your needs. Therefore, it’s your responsibility as the programmer to set the ACLs yourself. But what if you search through your favorite installation application’s documentation and find no mention of setting ACLs? You could just document the issue and politely ask your users to set the ACLs for you once your application finishes installing. Although this approach might seem like the easy way out, you will eventually receive complaints that your application is hurting system security, which can be embarrassing. A much better approach is to write a small application that sets the permissions properly and invokes that application from inside your setup routine—now your customers are always protected.

So what functions should you use in your application? The Win32 software development kit (SDK) documentation describes two sets of functions for setting security—low-level and high-level functions. There are also functions built specifically to work with Active Directory (AD) objects, but we’ll keep things simple for now. Although the high-level functions are generally easier to work with, they aren’t as flexible as the low-level functions. Working with the low-level functions also gives you a more thorough understanding of how these systems work. The low-level functions have the advantage of working on all versions of Win2K and NT, while some high-level functions apply only to the newer versions.

Each set of functions has various structures that you need to understand to write your application. The structures that the high-level functions use eventually resolve to the structures that the low-level functions use, so time spent understanding these isn’t wasted. The highest level structure you need to work with is a security descriptor, which is defined as follows:

typedef struct _SECURITY_DESCRIPTOR \{

   BYTE  Revision;
   BYTE  Sbz1;
   PSID Owner;
   PSID Group;
   PACL Sacl;
   PACL Dacl;

The revision number allows for future changes and is typically set to the defined value SECURITY_DESCRIPTOR_REVISION. The next byte is unused. The SECURITY_DESCRIPTOR_CONTROL member is actually a 16-bit set of flags that tell you whether a security descriptor has a discretionary access control list (DACL) or a system access control list (SACL), whether the owner or group is defaulted, and whether the security descriptor is absolute or self-relative. You don’t have to directly address most of these flags when setting an ACL, but it is worthwhile to understand the difference between an absolute and a self-relative security descriptor.

In an absolute security descriptor, the pointers to the Owner, Group, Sacl, and Dacl members refer to actual memory locations. In a self-relative security descriptor, the pointers refer to offsets within the security descriptor itself. Self-relative descriptors store the information in places like registry values, while absolute descriptors are usually easier to use within your program. Fortunately, several helper functions exist to convert one to the other, as well as abstract the details of obtaining the various attributes. The Owner member refers to the user who created the object (unless the user is an administrator, in which case the Administrators group is the owner), and the Group member is typically used only within the POSIX subsystem. The Sacl member determines the auditing settings on the object. Note that setting this member requires enabling certain user privileges. The Dacl member is the part we’re interested in setting.

The DACL consists of the following:

typedef struct _ACL \{
    BYTE  AclRevision;
    BYTE  Sbz1;
    WORD   AclSize;
    WORD   AceCount;
    WORD   Sbz2;
\} ACL;

Again, notice the revision number and unused byte. Next, the AclSize member tells you how many bytes this ACL contains, and the AceCount member tells you how many access control entries (ACEs) the ACL contains.

Finally, let’s examine what makes up an ACE. Several types of ACEs exist, some of which are new to Win2K. But the one that is most often used is the ACCESS_ALLOWED_ACE. The ACCESS_ALLOWED_ACE consists of the following:

typedef struct _ACCESS_ALLOWED_ACE \{
    ACE_HEADER Header;
    DWORD SidStart;

and the ACE_HEADER is defined as follows:

typedef struct _ACE_HEADER \{
    BYTE  AceType;
    BYTE  AceFlags;
    WORD   AceSize;

Let’s look at the ACE_HEADER first. The AceType member defines which type of ACE you have. So for an ACCESS_ALLOWED_ACE, you must set this member to ACCESS_ALLOWED_ACE_TYPE. There are several flags defined, but the most common of these are:

  • CONTAINER_INHERIT_ACE—this flag tells you that any new containers created within a container will inherit this ACE unless you set the NO_PROPAGATE_INHERIT_ACE flag.
  • INHERIT_ONLY_ACE—this flag means that the ACE can apply only to child objects, and does not control access to the object itself.
  • OBJECT_INHERIT_ACE—this flag tells you that any new non-container objects created within a container will inherit this ACE unless you set the NO_PROPAGATE_INHERIT_ACE flag.
  • NO_PROPAGATE_INHERIT_ACE—this flag isn’t often used, and it is a little tricky. When you set this flag, the ACE can be inherited only once; the system will clear both the CONTAINER_INHERIT_ACE and OBJECT_INHERIT_ACE flags to prevent the ACE from propagating any further.

An additional flag exists that applies only to Win2K, but for simplicity, we’ll skip those details for now. Last, the AceSize member tells you how many bytes the ACE needs for storage.

Now back to the ACE itself. The ACCESS_MASK member is a 32-bit value that tells you what types of access are allowed. I’ll provide some example code in my next column, where we can examine access masks more closely. Finally, the SidStart member represents the first four bytes of the SID for the user or group that this ACE affects. We won’t need to get into the details of the SID structure because there are convenient functions that take names and return the SIDs. Next time, I’ll present a simple application to illustrate how to use the background concepts I've covered in this column.