Although .NET provides its own security model (in the System::Security namespace) for CLR security, it doesn't address the lurking behemoth that is the Win32 security subsystem. For those who have programmed with Win32 security, you'll know that while it's conceptually straightforward, it's a real bear to program. For example, when writing a program that requires certain rights or privileges to work, it can often be a very intensive process to determine what privileges one has, or what rights are required.
In this article, I'll demonstrate how this coding headache can be simplified as I develop a .NET library, in Managed C++, that encapsulates the programming details in a simple object model. Along the way, we'll have a quick refresher on Win32 security, demonstrate some techniques for simplifying coding with the Win32 security APIs, and learn some of the restrictions and gotchas of Managed C++.
The article will be based on the first version of the .NET Win32 Security component I'm developing, and will describe the implementation of the AccessToken class and supporting types. It will also show how this object model can be utilized in a UI client, Security Explorer, written in C#.
Win32 Security Model Whistlestop
Win32 security is a big topic, which would require many articles to do it justice. (For those who want to learn more, I recommend Nik Okuntseff's book Windows NT Security as a good place to start.) I'm going to restrict myself in this article to a discussion of access tokens. An access token is created for each user when the user logs on, and persists for the duration of that user's session. Every process that the user executes receives a copy of that access token, which is then used by the security subsystem to determine what operations the process (i.e., the user) is allowed to perform.
An access token contains, among other things, the following items:
- The user's security identifier (SID). A SID is a variable-length binary structure that is used to uniquely identify entities (users and groups).
- A list of SIDs indicating the groups to which the user belongs.
- A default owner SID. This is assigned as the owning entity to objects that the user creates.
- A list of privileges. Note that some privileges must be enabled in order for them to be used.
- A default discretionary access control list (DACL). This is a list of permissions, known as access control entries (ACEs), that will be used when the user supplies a null SECURITY_DESCRIPTOR during system object creation (e.g., the fourth parameter in CreateFile()).
- The creation and manipulation of some of these structures can be very cumbersome indeed. For example, an access control list (ACL) consists of a header the Win32 structure ACL followed by a variable number of access control entries (ACEs). Each ACE is a variable-length structure consisting of a header ACCESS_ALLOWED_ACE, ACCESS_DENIED_ ACE, or SYSTEM_AUDIT_ACE all of which are (thankfully) the same size, and comprised of an ACE_HEADER structure, an ACCESS_MASK, and a SID (which is the variable length part). And that's just an ACL. A security descriptor, which is the security information applied to system objects (e.g., files, registry keys, processes, etc.) is comprised of two SIDs (owner and group) and two ACLs (permissions list and auditing list), all of which are variable length, and may or may not be comprised within the same memory block as the security descriptor or referenced via pointers. Scary, huh?
The SynSoft::Security::AccessToken class is defined, in Managed C++, as shown in Listing 1. The first thing you'll note is that it derives from the interface System::IDisposable. This interface marks the class as implementing the Dispose pattern, which means that the class can be used in a C# using expression (providing Resource Acquisition Is Initialization, albeit user-requested rather than class-mandated) and also is amenable to generalized closure: One simply dynamically casts (using dynamic_cast in C++ or as in C#) to IDisposable, then calls Dispose() on a (nonnull) resultant reference. AccessToken, like any class that owns/manages a native resource (in this case the access token) provides this facility so that well-behaved clients can cause it to relinquish its resources in a timely fashion, rather than waiting for garbage collection. Furthermore, it is my practice to provide a Close() method whenever implementing IDisposable, which performs the same task as Dispose(). You'll see this in other classes in this library.
Next, it is clear that one cannot construct an instance of an AccessToken. This makes sense, since we cannot create an access token in Win32 land we can only open one via the Win32 functions OpenProcessToken() and OpenThreadToken(). AccessToken has two corresponding static methods that do exactly the same job, and then return an AccessToken instance representing the (successfully opened) token. So we can open a token and we can close/dispose of it. Now what we're interested in are its attributes.
The current version of this library contains read-only components. (Create/edit functionality is planned in a future release; visit http:// synsoft.org/dotnet.html to obtain updates.) We can access, as .NET properties, the user and primary group, the groups, the privileges, the default DACL, and a variety of other attributes associated with an access token. The types returned (e.g., SID, GroupList, PrivilegeList) are also classes in the same namespace (and assembly). Most of these classes are also required to implement the IDisposable pattern; this is one of the hassles of .NET's not supporting C++'s deterministic destructors. Consider the GroupList class (Listing 2). This class does not contain any unmanaged resources. In fact, its only member is an ArrayList. However, because the list is populated with Group instances in the GroupList constructor, GroupList must implement the IDisposable interface, and in its Dispose() method must iterate through the list ArrayList does not implement IDisposable and call Dispose() on each contained object (in this case, Group instance). This stuff is tedious, and also carries a bit of a catch. Sometimes a class will implement IDisposable but will make its Dispose() method nonpublic; this is known as Explicit Interface Member Implementation. Hence, you need to cast the contained elements to IDisposable before attempting to call Dispose(). Because this is a common scenario, I've used the .netSTL (.netSTL is a subproject of STLSoft; http://dotnetstl.org/) algorithm dispose_contents<>() (shown in Listing 3, with its supporting function dispose_set_null<>()).
The picture doesn't end there, alas. Group also does not contain any unmanaged resources, but it has an instance of class SID, which does a Win32 SID blob, in fact (see Listing 4). Thus, we had to add Dispose() (and Close()) functionality to at least two other types because a composed type requires it. Of course, you may think why bother, the Win32 SID is only a blob of memory (albeit from the native rather than the managed heap)? Well, as a rule, I don't feel comfortable having any unmanaged resources hanging around for an indefinite time, and one might ponder the case where the resource is not a block of memory but a file handle. This is a fundamental problem of garbage-collected systems; sure, they handle memory fine (often better than deterministically managed systems), but what about all the other resource types (files, pipes, and heaven-forefend, synchronization objects!)?
Having had my C++-biased rant, let's see what's good about this library. Looking into the implementation of many of the property accessor methods of AccessToken (Listing 5), we can see one nice feature. Since all managed types must be held by pointer, the implementation dilemma common in object models do I use composition to reduce memory usage and increase execution speed, or use pimpl to reduce memory usage and increase execution and compilation speed? is moot. We have to use pointers, and it's often best to choose lazy evaluation. This may or may not be the "best" thing from a pure programming perspective, but it simplifies the design, coding, and maintenance of modules, so it has a lot going for it. A side effect is that it will help to ameliorate the dangling unmanaged resource issue; in cases where they are not used, they're not created, and so not dangling.
So how do we get information out of an access token? GetTokenInformation(), of course. This very powerful function is prototyped as follows:
BOOL WINAPI GetTokenInformation (
You pass a token handle, the type of information you want (a member of the TOKEN_INFORMATION_CLASS enumeration), a destination buffer and its length (in bytes), and an address to receive the number of bytes of information retrieved. Seems straightforward, but like most Win32 Security API functions, it can be tricky. Some of the access token information is of a fixed size, and so one simply passes the appropriate parameters, as can be seen in the implementation of AccessToken::get_Type(). In most cases, however, the information is of variable length, so one must call GetTokenInformation() as described in the sidebar titled "Surviving the Win32 Security API." This approach is repetitive and error-prone what if there is an exception thrown between the allocation and deallocation of the resource? A lot of careful boilerplate is required (see AccessToken::_get_groups() in Listing 5; this implementation is chosen because both TokenGroups and TokenRestrictedSids return information in a TOKEN_GROUPS structure). Mercifully, there is a simple and elegant solution in the form of the WinSTL (another STLSoft subproject; http://winstl.org/) component token_information<> (see Listing 6). You parameterize the template using the appropriate TOKEN_INFORMATION_CLASS enumeration member, and the traits mechanism automatically deduces the correct data structure type and instantiates a class that wraps the allocation, acting as a smart pointer for the requisite type. The get_User() and get_Privileges() methods in Listing 5 demonstrate how much this simplifies matters. (Note that the template is flexible in allowing you to specify an exception policy. The default is stlsoft::null_exception, which does not throw, in which case the implementation ensures that GetLastError() will reflect the failing condition, rather than being overwritten during deallocation of the memory buffer.)
The difficulty of processing the information returned depends on the type of information retrieved. Retrieving SECURITY_IMPERSONATION_LEVEL, which is an enumeration (as is TOKEN_TYPE), is very simple. Just retrieve it and convert it to the appropriate managed enumeration type. Note: It's convenient, but somewhat unsafe, that Managed C++ lets you cast, in the form of a conversion constructor, from a C-type to a managed enumeration type; see AccessToken::get_Type(). Although I've borrowed the values for the managed enumerations TokenType, TokenImpersonationLevel, and SidType from their native equivalents, and am confident I've transcribed them correctly, what if I got it wrong?)
For some variable sized entities, it is also reasonably easy to process the retrieved information. Retrieving groups or privileges returns counted arrays of SID_AND_ATTRIBUTES and LUID_AND_ATTRIBUTES, respectively, so one need simply walk the array, as in the GroupList constructor (Listing 2).
Access Control Lists and Entries
The retrieval of the default DACL from an access token is an altogether more complex matter. As well as retrieving a variable length structure, the items within the structure are polymorphic (can be one of the three ACE types), and are themselves of variable length. Nasty.
The answer comes from a combination of another useful class from WinSTL the acl_sequence<> template (not shown; available from http://winstl.org/downloads.html) and a hacky assumption. Since all the ACE structures have the same essential structure (which can't be a coincidence I bet the designers of the Win32 Security API decided in this one small way to give themselves a break), we can treat them as sharing a pseudotype (wherein ACE_HEADER) to treat them all equally. Listing 7 shows the solution in the ACL constructor. acl_sequence<> takes care of defining the range [begin, end), and ensuring the appropriate pointer-arithmetic when moving from one ACE to the next. Inside the loop, we switch on the header type, and then call the appropriate ACE constructor. AccessAllowedACE, AccessDeniedACE, and SystemAuditACE share a common base class, ACE, which handles the retrieval of the ACE structure contents into the class in a generalized (based on their common layouts) fashion. As I said, it's a hack, but since it would be impossible to change the sizes of the extant ACE types without breaking every Win32 program out there that uses security, I think we can say it is an informed hack and sleep soundly.
That's pretty much all the ugly stuff in the library; the rest is very straightforward and I'll leave it to you to download the archive and look through it. Let's see how we can use it. The Security Explorer program is shown in Figure 1. As you can see, it has a standard tree/list format, where the tree contains composite or collection entities, and the list contains the values or attributes. This first version allows exploration of the process access token. The root-most node, when selected, displays the user, owner, primary group, and the like, and then the five subnodes show the contents of each of their collections.
The Security Explorer works by opening the access token for the process, by calling AccessToken::OpenProcessToken. It stores this along with an identifying type in an instance of a NodeItem type, which is then stored as the Tag value (LPARAM in Win32 API parlance) of the tree node. The collection properties (Groups, Privileges, etc.) are similarly stored in subnodes. When the user clicks on a node, the handler loads the stored object (and its type), and processes it according to the type. It's not particularly elegant a more professional solution would be to use polymorphic display-handler classes but it serves to exercise the security object model. I'm already finding this useful, since it's far and away the quickest mechanism I have to tell me what privileges I have, and whether they're enabled, on a given system.
This article has given a very brief introduction to the Win32 security subsystem, discussing in some detail the contents of access tokens and how to read them. Hopefully, this has whetted your appetite and shown you that, in combination with suitable helper code, manipulating the security API is not as scary as you might have thought. We all avoid it like the plague as a rule, but when you need to secure your application objects, you have no choice but to dive in. Now, perhaps, you'll have a bit of background info to help.
We've also discussed some of the implications of development in Managed C++, and highlighted some of the issues you encounter with this new language. Finally, I hope the point is not lost that we've tamed a daunting C API by applying some very modern C++ (STL) techniques. Over this, we've layered a Managed C++ object model, which was then used in a C# application. How's that for integration?
Notes & References
Applied .NET Framework Programming. Jeffrey Richter, Microsoft Press, 2002.
Windows NT Security. Nik Okuntseff, R & D Books, 1997.
STLSoft is an open-source organization applying STL techniques to a variety of technologies and operating systems. It is located at http://stlsoft.org/. WinSTL and .netSTL are subprojects, and are located at http://winstl.org/ and http://dotnetstl.org/, respectively.
SynSoft is the nonprofit imprint of my company, Synesis Software, and is located at http://synsoft.org/. It provides D, .NET, Java, Perl, and Python code/components free for use without royalty.w::d
Matthew Wilson holds a degree in Information Technology and a Ph.D. in Electrical Engineering, and is a software development consultant for Synesis Software. Matthew's work interests are in writing bullet-proof real-time, GUI and software-analysis software in C, C++, C#, and Java. He has been working with C++ for over 10 years, and is currently bringing the STLSoft project into the public domain. Matthew can be contacted via email@example.com or at http://stlsoft.org/.