Security Briefs

Access Control List Editing in .NET

Keith Brown

Code download available at:SecurityBriefs0503.exe(171 KB)

Contents

Exploring ISecurityInformation
GetAccessRights and Friends
Runtime Modeling
The Managed Adapter
Memory Management
Conclusion

Access control lists (ACLs) can be complex beasts, and user interfaces for editing them are incredibly tricky to implement properly. That's why I was really excited when Windows® 2000 shipped with a programmable ACL editor, shown in Figure 1. Technically this is more than an ACL editor; it also allows you to edit the owner of an object and both the discretionary and system access control lists (DACLs and SACLs, respectively). But I'll call it "ACL editor" here rather than the more technically correct "Security Descriptor Editor."

Figure 1 Permissions Editor Property Sheet

Figure 1** Permissions Editor Property Sheet **

If you have been reading Security Briefs since its inception in 1999, you may remember a column I wrote in May of that year that demonstrated the ACL editor. Until now, you had to be a C++ programmer to use this feature in your programs, but I've recently completed a cool little adapter that allows you to use the ACL editor from C#, Visual Basic® .NET, or any other managed language.

There are a number of reasons why you might want to do this. If you're building a tool for deploying software, you often need to set ACLs on files. And it's often impossible to know ahead of time what those ACLs should look like: that's something only the system administrator will know when she's deploying your software. An ACL editor is a great way to gather that information for use in a later automated deployment.

Another reason is that you might want to view and edit ACLs for objects that don't yet have an ACL editor built into Windows. The sample application that comes with this column solves this problem for services. And finally, a few of you might use private ACLs for your own objects. In this case, an ACL editor is one of the key features in your application!

In order to add the ACL editor to your program, you must implement an unmanaged interface called ISecurityInformation. This is not a .NET-friendly interface by any means. In fact, it's not even a COM-friendly interface, as I'll discuss later. But using my adapter, you can implement a managed version of this interface. With this adapter, you can add basic ACL editing to your program in approximately a half an hour.

Before you start, however, you need to know how ISecurityInformation works and how to implement its five or six core methods because the managed version you'll implement with my adapter is a direct mapping of the corresponding methods. The types have been made friendlier to the managed world, but the concepts are all the same. So let's first look at ISecurityInformation in detail.

Exploring ISecurityInformation

The model behind the ACL editor GUI is really quite straightforward. Your job is to implement an interface that describes what it is the GUI is supposed to be representing, including strings that describe what each bit in a permission mask means, as well as the features you want the editor to expose. You also must respond to queries about the current state of the security descriptor such as who the owner is and what the DACL looks like, as well as handle updates submitted through the GUI.

The code in Figure 2 shows the seven methods that make up ISecurityInformation interface. Let's walk through a typical scenario so you can see when the various methods are called and learn how to respond to each request.

Figure 2 ISecurityInformation

interface ISecurityInformation : IUnknown { void GetObjectInformation(SI_OBJECT_INFO* pObjectInfo); void GetSecurity(SECURITY_INFORMATION si, void** ppsd, BOOL wantDefault); void SetSecurity(SECURITY_INFORMATION si, void* psd); void GetAccessRights(const GUID* type, DWORD flags, SI_ACCESS** access, ULONG* count, ULONG* defaultIndex); void MapGeneric(const GUID* type, UCHAR* aceFlags, ACCESS_MASK* mask); void GetInheritTypes(SI_INHERIT_TYPE** inheritTypes, ULONG* count); void PropertySheetPageCallback(HWND hwnd, UINT uMsg, SI_PAGE_TYPE type); };

The first method the ACL editor calls is GetObjectInformation. This method allows you to specify a whole host of options about how you want the editor to work by filling out a data structure called SI_OBJECT_INFO:

typedef struct _SI_OBJECT_INFO { DWORD dwFlags; HINSTANCE hInstance; LPWSTR pszServerName; LPWSTR pszObjectName; LPWSTR pszPageTitle; GUID guidObjectType; } SI_OBJECT_INFO, *PSI_OBJECT_INFO;

Note that I'm showing the Win32® version of this data structure instead of the managed type I wrote for use with the adapter. I'll stick with this convention as it'll help you find your way around the official documentation. But the mappings to my managed types are obvious, so you shouldn't have any problem using them once you've finished reading this column.

The first member of this structure is the most important, the flags. There are a dozen or so flags defined, and I'll walk you through some of the more commonly used ones.

SI_EDIT_PERMS controls whether the object's DACL should be viewed and edited. You cannot turn off this flag (its value is zero), since this is the core of the editor's functionality.

SI_EDIT_OWNER controls whether the object's owner should be viewed and edited. If you specify this, the Advanced dialog will include an Owner tab for modifying the owner of the object.

SI_EDIT_AUDITS controls whether the object's SACL should be viewed and edited. This adds an Auditing tab. Be careful about implementing this one—you should check to see if the user has SeSecurityPrivilege before specifying this flag, otherwise when the user clicks the tab and the ACL editor asks you to look up the object's SACL, you'll have to throw an exception, since without this privilege, you're not allowed to even read the SACL! It's better not to have this tab show up at all if the user doesn't have this privilege. My adapter handles this for you automatically by removing this flag if the user doesn't have the correct privilege.

Both the owner and audit flags only have meaning if you also specify SI_ADVANCED, which allows the user to get to the Advanced security dialog in the first place.

SI_CONTAINER indicates that the object being edited may have children that inherit permissions from this object. A file system folder or registry key is an example of a container, while a file is not. This flag helps the editor determine whether to show or hide features related to inheritance.

SI_READONLY is very useful. It tells the editor to allow viewing of the various parts of the security descriptor you've specified, but doesn't allow editing. The buttons for making changes will all be disabled if you set this flag.

SI_RESET displays a Default button that allows the user to reset the security settings to a default state that you can choose. This is what the second argument to GetSecurity is for; it tells you whether the ACL editor is requesting the actual security information for the object or the default settings that should be used when the Reset button is pressed. If you don't use this flag, you can ignore that parameter to GetSecurity.

SI_PAGE_TITLE allows you to control the text that shows up in the title bar via the pszPageTitle member of SI_OBJECT_INFO.

The pszObjectName argument should be set to a short name of the object, as it will be used by the editor to build the title bar text for many dialogs.

The pszServerName argument is used to determine the machine that will convert Security Identifiers (SIDs) to names and perform other security functions. This argument should be set to the machine name where the resource you're editing actually lives to enable any local user accounts and groups to be processed correctly. Typically this will be the local machine, in which case you can set this argument to NULL.

Unless you're editing Active Directory® ACLs, you'll not likely need to specify the type GUID, which only applies to directory style access control lists.

GetObjectInformation may be called several times during a session with the ACL editor. In a recent test I ran on Windows XP, this method was called three times before the GUI even popped up on screen for the first time. This can cause problems if you're not ready for it, so be consistent with the answers you give from this function (don't change your mind halfway through about what flags you're using), and be careful with memory management if you ever implement this unmanaged interface yourself. If you use the adapter presented here, I've taken care of the memory management issues for you. But more on that later.

GetAccessRights and Friends

The next method that is typically called is GetAccessRights, which is where you specify the permission masks that the object supports, along with short descriptions that the GUI will display for you. Figure 3 shows how these strings are displayed in the advanced permissions dialog. You communicate this information to the ACL editor via an array of SI_ACCESS data structures:

typedef struct _SI_ACCESS { const GUID* pguid; ACCESS_MASK mask; LPCWSTR pszName; DWORD dwFlags; } SI_ACCESS, *PSI_ACCESS;

Figure 3 Advanced Permissions Dialog

Figure 3** Advanced Permissions Dialog **

Once again, unless you're working with Active Directory ACLs, you won't need to specify a GUID here. The mask variable holds a 32-bit value with one bit set, indicating the permission being described. In a service ACL for example, 0?10 represents the permission to start the service, while 0?20 represents the permission to stop it. Along with each permission, you specify a set of flags detailing where that permission should appear, and how (if at all) it interacts with ACL inheritance schemes. I don't have room to cover all the inheritance options here, but you can read more about ACL inheritance in my online book at What is ACL Inheritance.

There are a couple of flags that you'll always need to consider, as they control where the permission shows up in the editor. You see, the ACL editor displays a concise, summarized view of an object's DACL on its main page. This summary doesn't show all the details, and many permissions are collapsed into more easily understandable categories. For example, compare Figure 1, the summary view, with Figure 3, the advanced view. Note how the advanced view details each permission explicitly, while the summary view groups whole sets of permissions together.

For a service, for example, the summary view permission called Execute is actually composed of several more detailed permissions: Start, Stop, Pause or Continue, and so forth. In the case of these summary permissions, more than one bit is set in the mask—one bit per permission that makes up the summary. For example, the mask for Execute permissions is 0x00020170, the combination of five more detailed permissions.

To specify that a permission should show up on the summary page, use the SI_ACCESS_GENERAL flag. For the details page, use the SI_ACCESS_SPECIFIC flag. On rare occasions you'll want an entry to show up in both places: the classic example is the Full Control entry. In this case you'd specify both of these flags. And just to give you a practical tip for usability, you'll definitely want to include Full Control in both places. It's really helpful in the advanced view, because when you check a box for one of these combined permission entries (like Full Control), the editor will automatically check all the permissions in the mask. If you're not sure what I mean, try to grant or deny full control using the advanced editor (see Figure 3) without having a Full Control option. You'll develop a callous on your finger clicking all those little boxes!

When you are implementing GetAccessRights, you also specify the entry in the list that represents the default permission mask. Generally you'll want to set this to the general Read permission entry. This comes into play when a user adds a new entry to the DACL. She is asked to select a user or group for the entry (in other words, to whom is she granting permissions?), and that new entry shows up in the list with whatever default permissions you specified.

The code in Figure 4 shows an example implementation, which is part of an application that allows you to edit service ACLs, so all permissions that apply to services are shown. These include Start, Stop, Change Configuration, and so on. This implementation sits behind my adapter, so it uses managed types, but the ideas are exactly the same.

Figure 4 Implementing GetAccessRights

public AccessRights GetAccessRights(Guid objectType, MsdnMag.Security.Adapters.ObjectInfoFlags flags) { AccessRights rights = new AccessRights(); Access[] access = rights.Access = new Access[17]; // these aliases should make the code more readable // for the printed edition (we don't get much width!) AccessFlags G = AccessFlags.General; AccessFlags S = AccessFlags.Specific; // summary page permissions access[0] = new Access(SERVICE_ALL, "Full Control", G | S); access[1] = new Access(SERVICE_READ, "Read", G); access[2] = new Access(SERVICE_WRITE, "Write", G); access[3] = new Access(SERVICE_EXECUTE, "Execute", G); // advanced page permissions access[4] = new Access(0x0001, "Query Configuration", S); access[5] = new Access(0x0002, "Change Configuration", S); access[6] = new Access(0x0004, "Query Status", S); access[7] = new Access(0x0008, "Enumerate Dependents", S); access[8] = new Access(0x0010, "Start", S); access[9] = new Access(0x0020, "Stop", S); access[10] = new Access(0x0040, "Pause or Continue", S); access[11] = new Access(0x0080, "Interrogate", S); access[12] = new Access(0x0100, "Send User Defined Control", S); access[13] = new Access(0x00010000, "Delete", S); access[14] = new Access(0x00020000, "Read Permissions", S); access[15] = new Access(0x00040000, "Change Permissions", S); access[16] = new Access(0x00080000, "Take Ownership", S); // note how I refer to access[1] as the default ("Read") rights.DefaultIndex = 1; return rights; }

Between GetObjectInformation and GetAccessRights, you specify virtually all of the details the ACL editor needs to configure its display. For container objects, there's a third method that the ACL editor will invoke if the user drills down into the advanced permission editor. This method is called GetInheritTypes, and it's where you provide human-readable descriptions for the various inheritance options. This is the disabled listbox titled "Apply onto" that is shown in Figure 3. You'll see this in the chapter on ACL inheritance that I mentioned earlier.

Runtime Modeling

The rest of the functions you're going to see have to do with managing the actual security descriptor being edited or viewed. Let's first look at GetSecurity (refer to Figure 2). The ACL editor calls this method whenever it needs to know the owner, DACL, or SACL for the object being edited. For example, right before the ACL editor appears, it will call GetSecurity to ask for the DACL, for its initial summary display. If the user presses the Advanced button (assuming you've allowed the editor to show it), GetSecurity will be called again, as the DACL is being displayed in another form. When the user clicks the Owner tab, GetSecurity will be called yet again to get the owner. The same thing happens on the Auditing tab, so expect this function to be called several times during any editing session.

If the user makes a change and commits it by pressing Apply or OK, the ACL editor gives you the new data by calling SetSecurity. It's your job to provide a security descriptor for editing. In many of these cases you can simply forward these requests to corresponding Win32 functions such as GetNamedSecurityInfo and SetNamedSecurityInfo, as I do in my service example. My GetSecurity implementation literally asks the operating system for the requested information and passes it back to the ACL editor, and my SetSecurity implementation writes the changes directly to the service.

If you're editing a private security descriptor for a custom object of your own design, a rather advanced technique discussed in Chapter 6 of my first book, Programming Windows Security (Addison-Wesley Professional, 2000), you'll need to read and write this data via whatever store you happen to be using in your application. I've seen private security descriptors stored in database columns, binary registry keys, or encoded as Security Descriptor Description Language (SDDL) in XML files.

At run time you'll likely see another rather esoteric function being called several times: MapGeneric. This function allows the ACL editor to ask you what the four generic permissions mean for the class of object being edited. GENERIC_READ represents permissions you'd grant if you were giving read-only access to your object. GENERIC_WRITE are the additional permissions you'd grant if somebody needed basic read/write access. GENERIC_EXECUTE applies to objects that have execution semantics (processes, services, and so on) and represents whatever extra permissions you'd grant if you wanted to allow someone to execute or control the execution of the object. GENERIC_ALL is just what it sounds like—all possible permissions for the object.

These generic permissions are often granted by default to objects. For example, whenever you create a new process, SYSTEM is granted GENERIC_ALL permission to that process by default. If you want further information on generic permissions, here's another link to my online book: What Is A Permission.

The simplest way to implement MapGeneric is to fill out a data structure (called, aptly enough, GENERIC_MAPPING) that maps from the four generic permissions onto four corresponding permission masks. Then you can implement the MapGeneric method by calling the Win32 function MapGenericMask. The sample code that accompanies this column shows how to accomplish this—it's quite easy. The hard part is figuring out what the mapping is. For objects that are built into the operating system, like services, you can find the generic permission mappings in the Platform SDK documentation (which you should have if Visual Studio® .NET is installed on your machine).

For example, to find the service permissions along with their generic counterparts, search for "service GENERIC_READ" while filtering on "Platform SDK". For the actual values of each permission, you'll need to look at the corresponding Win32 header file (in this case, winsvc.h). The header file winnt.h contains many of the kernel permission definitions, along with some of the generic mappings. This is more of an art than a science right now, but believe it or not, it's gotten a lot better than it was a few years ago when you had to guess what the generic mappings for most objects were.

There's just one last function on ISecurityInformation that I have not yet covered: PropertySheetPageCallback. This function is useful if you're incorporating the property sheet into a larger dialog box and want to customize its behavior. This isn't currently possible in a Windows Forms application though, so I won't spend any time covering this method. In fact, my managed adapter just stubs this method out.

The Managed Adapter

When I first tried to use ISecurityInformation from managed code, I immediately realized that it would be a challenge. For one thing, the interface isn't specified in any type library that I can find. And you know, that's probably a really good thing because, besides the use of HRESULT and IUnknown, it really doesn't behave much like a COM interface at all. If you're one of the folks who remembers hacking up COM implementations from scratch in C++ or even ATL, you'll remember all the careful rules COM had about memory management. When someone calls your COM interface and asks for a dynamically sized string (or even an array), you allocate the memory and the code that called you frees it. And there are special functions that COM provides for doing this, although I won't drag you back to yesteryear by listing their names.

Well, ISecurityInformation breaks these rules all over the place in the name of efficiency. For example, there are all kinds of strings that you create and give to the ACL editor when it calls GetAccessRights. The ACL editor never frees these strings because ISecurityInformation was designed with the assumption that a C++ programmer would be using it and could simply pass back pointers to static strings that never needed freeing. Sadly these sorts of practices, since they violate the fundamental rules of COM, mean that COM interop simply doesn't work with ISecurityInformation. At least not without a lot of intervention on your part.

So, as I find myself doing a lot these days with version 1.1 of the .NET Framework, I dropped down into managed C++ and wrote an adapter. The idea behind this was simple: I created some easy-to-use managed types that map as directly as possible onto the types used by ISecurityInformation. I then created a new interface called ISecurityInformationManaged that uses these new types, and implemented a custom COM-callable wrapper (CCW) that does the translation.

The following code shows the new, managed interface:

public interface ISecurityInformationManaged { ObjectInfo GetObjectInformation(); AccessRights GetAccessRights(Guid type, ObjectInfoFlags flags); byte[] GetSecurity(SecurityInformation info, bool wantDefault); void SetSecurity(SecurityInformation info, byte[] sd); void MapGeneric(GenericAccess generic); InheritType[] GetInheritTypes(); };

This is the interface you will implement if you use the adapter. I think you'll find it remarkably easy to use. The only type that I didn't bother translating into managed code is the SECURITY_DESCRIPTOR, which is a very complicated beast. Fortunately, you can usually live a rich, happy life just treating this as an opaque blob, so I modeled it with a byte array. When the .NET Framework 2.0 gets a bit closer to shipping, I'd be happy to tweak this adapter to use the new System.Security.AccessControl namespace that has a very nice model of SIDs and security descriptors.

Memory Management

Writing this adapter turned out to be a somewhat complicated exercise in memory management. Because the ACL editor doesn't free most of the strings and data structures given to it, the adapter's main job is tracking dynamically allocated native memory so it can be freed after the ACL editor closes. It would be great if I could simply hand direct pointers to managed strings to the editor, but that would be a disaster as managed objects move around in the heap due to compaction during garbage collection. Each string that you give the adapter via ISecurityInformationManaged must first be marshaled (copied) into a native string, and tracked until it's no longer needed; that is, when the ACL editor closes and releases the adapter.

My current implementation is very simple. Each string that I marshal into native code is simply pushed onto a linked list (I use an STL vector at this time), so that they can be freed when the adapter is released. Seems reasonable, don't you think?

Well, after some testing, I found that this technique does not use space very efficiently, and actually leaks memory while the editor is in use. Remember how I said that the editor calls GetObjectInformation and GetAccessRights many times during a session? I'm specifying the exact same access permission descriptions each time, but I'm marshaling them again and again, and my list of strings gets longer and longer the more times these functions are called. In practice this probably won't be a problem, since the editor won't be used long enough to drain the system of enough memory to be noticeable. The adapter frees this memory when it's released as the ACL editor closes. When I get some spare cycles, I'll probably switch this over to use a map instead of a vector so that I only store any given string once.

Conclusion

As I mentioned earlier, this was mainly an exercise in memory management, and the code download that accompanies this column contains all of the source files. But this adapter should help you if you're using Visual Basic .NET or C# and were otherwise unable to add ACL editing capability to your projects.

I'd appreciate any feedback, bug reports, improvements, and so on.

Send your questions or comments for Keith at  briefs@microsoft.com.

Keith Brown is a co-founder of Pluralsight, specializing in developing and delivering high quality training for software developers. Keith's most recent book, The .NET Developer's Guide to Windows Security.