Security

Manipulate Privileges in Managed Code Reliably, Securely, and Efficiently

Mark Novak

Parts of this article are based on a prerelease version of the .NET Framework 2.0. All information pertaining to those sections is subject to change.

This article discusses:

  • Manipulating token privileges from managed code
  • Application domain isolation
  • Performance and kernel transitions
  • Constrained execution regions
This article uses the following technologies:
Security, C#, and the .NET Framework

Code download available at:TokenPrivileges.exe(140 KB)

Contents

What Are Privileges?
Privileges in Unmanaged Code
Design Overview
Challenge 1: Living Inside Application Domains
Challenge 2: Changing Multiple Privileges
Challenge 3: Performance
Challenge 4: Deterministic Execution
Challenge 5: Safe Encapsulation of Windows NT Handles
Improving the Privilege Class

Like many developers of managed code, I frequently interact with system internals. One day, while working on the managed access control list (ACL) class library for the Microsoft® .NET Framework 2.0 (see my article in the November 2004 issue of MSDN®Magazine), I had to implement support for changing a security descriptor on an object. In some situations, this operation requires enabling Take Ownership and Security privileges. I poked around and discovered that there was no existing support in the .NET Framework for performing this task.

Simple enough, I thought—a P/Invoke of the AdjustTokenPrivileges API, and I'd be on my way. Little did I know how complex this task would become. I quickly discarded my original implementation due to several critical flaws, and about a dozen iterations later, I bring you the story of one of the coolest system programming quests in my recent memory.

This article contains two solutions to the problem: the first, simpler one, is tailored to the .NET Framework 1.1 and can be used today. The second solution incorporates several advanced features available only in the .NET Framework 2.0. Regardless of which version of the Framework you are developing for, and irrespective of whether you want to understand the many technical complexities of the implementation, the solution presented here is going to be a valuable addition to your programming toolbox.

What Are Privileges?

MSDN defines a privilege as the right of an account, such as a user or group account, to perform one of several system-related operations on the local computer, such as shutting down the system, loading device drivers, or changing the system time. There are important differences between privileges and access permissions or rights. In some cases, privileges can be thought of as a way to circumvent security protections placed on various operations by the operating system. In others, they allow one to perform system tasks that are not related to manipulating individual objects. For instance, a file's ACL may disallow a particular account (FABRIKAM\Joe) the right to read the file, yet Joe may still be able to read the file if he possesses the Backup privilege. Similarly, the Restore privilege will allow Joe write access to a file, even if the file's ACL forbids it.

The other aspect of privileges that makes them stand out is the way they are assigned. Whereas group membership is decided by either Active Directory® (and computed on domain controllers) or local Security Accounts Manager (SAM), privileges are always computed and assigned to the user by the local system, according to the local machine's policy (of course, group policy may be used to push privilege assignment onto the local machine in order to change the local policy). Finally, unlike security descriptors which are attached to individual objects, privileges are global in scope—they are part of security tokens, and apply universally to anything a user does on the machine he or she is logged on to.

To better understand how privileges work, consider the case of user FABRIKAM\Joe accessing a file on the system. In my example, Joe requests WRITE_OWNER (take ownership) access to the file. The access check algorithm will first establish the caller's security token. For that, it will attempt to open the thread token. Here, you may encounter one of two conditions: either the current thread is impersonating (meaning the thread has a security token associated with it) or it is not. By default, threads do not have security tokens associated with them, and the system proceeds to open the process token, an operation that is unlikely to fail.

The algorithm for checking the security descriptor will first attempt to grant all the rights it can, based on the currently enabled privileges in the security token. The WRITE_OWNER right we are interested in here will be granted if the Take Ownership privilege is enabled. WRITE_OWNER may also be granted if there is an access control entry (ACE) in the discretionary access control list (DACL) that grants the right explicitly. This (simplified) view of the access check algorithm is illustrated in Figure 1.

Note the clause "currently enabled privileges." It is not enough for a privilege to merely be present in the security token: it must also be in the "enabled" state for it to take effect. Whenever a privilege is present, it can be enabled or disabled at any time, so this mechanism is used as a safeguard against accidentally using an existing privilege to perform some action, a classic example of "running with least privilege."

In order to perform a privileged operation, your first step is to run code that enables the necessary privilege, remembering the initial state as it was before the privilege was enabled. Then you run the code that requires the privilege you just set, then revert the privilege back to its initial state.

Figure 1 Checking for Privilege

Figure 1** Checking for Privilege **

Note that the last step here is not disabling the privilege but reverting it. This is because the privilege could have already been enabled, either by default or by an earlier explicit action and it would be bad form to disable it if so.

In my example, if Joe possessed (and remembered to enable) the Take Ownership privilege prior to opening the file for WRITE_OWNER access, the access check would succeed regardless of what the file's ACL had to say on the matter.

Privileges in Unmanaged Code

At its core, the task of changing the state of a privilege on a token has little to do with managed code; it is a sequence of system API calls. Understanding the required APIs and the proper invocation protocol is the first step toward creating a managed code implementation of this functionality.

All privileges have human-readable names. For instance the Take Ownership privilege is usually referenced by its name: SE_TAKE_OWNERSHIP_ NAME or SeTakeOwnershipPrivilege. However, in order to make a call to the API which manipulates token privileges—AdjustTokenPrivileges—the privilege must first be converted to its corresponding locally unique identifier (LUID). This is done with a call to a special API: LookupPrivilegeValue. The LUID corresponding to a particular privilege name does not change for as long as a system is up, so it makes sense to cache the LUID value if you are going to touch the privilege more than once.

Before AdjustTokenPrivileges can be called, you must obtain a security token that will become the target of manipulation. For reasons having to do with application domain isolation that I'll describe later, it's not proper to modify the process token, although the Windows® API does allow it. Therefore, you start by trying to obtain the thread token with a call to OpenThreadToken. Due to the Windows NT® security architecture, threads normally run without a security token, defaulting it to the process, so the initial call to OpenThreadToken will likely fail with ERROR_NO_TOKEN. In this case, the right thing to do in order to avoid messing with the process state is to place a copy of the process's security token on the thread, an operation called impersonation. There are several ways to do this (you can call ImpersonateSelf or ImpersonateLoggedOnUser, for instance), but for reasons described later in a section that deals with performance issues, I'll take a different approach. Open the process token with OpenProcessToken, duplicate this token handle with DuplicateTokenEx, and use the output of the latter to complete the impersonation with a call to SetThreadToken. After this step, the thread is impersonating and you can complete the operation by calling the API designed for changing the privileges on a token—AdjustTokenPrivileges. This process is illustrated in Figure 2.

Figure 2 API Calls

Figure 2** API Calls **

As you can see, the call to LookupPrivilegeValue converts the privilege name into a LUID, after which OpenThreadToken checks whether the thread is impersonating (though usually it isn't). OpenProcessToken gets the process security token (requesting DUPLICATE_HANDLE access) and DuplicateTokenEx duplicates the process token handle, obtaining another impersonation token with rights sufficient for subsequent operations (impersonate, query, and adjust token privileges). The SetThreadToken method sets the copy of the process handle on the thread, such that the thread is now impersonating. Finally, AdjustTokenPrivileges takes as its parameter the new state of the privilege, and returns the initial state.

You must be careful to remember this initial state, otherwise it would be impossible to revert to it once the privilege is no longer required. Once the user is ready to revert the privilege, AdjustTokenPrivileges can be called again, this time passing as the new state what was returned earlier as the initial state of the privilege. If you started by impersonating, the thread can be reverted to its original impersonation state by calling RevertToSelf.

Things are pretty simple so far. If the logic was confined to unmanaged code, you'd be finished by now and there would be no need for an entire article on the subject. The real fun starts when porting these ideas to managed code.

Design Overview

The class for manipulating privileges that I'm going to develop here will have the public interface illustrated in Figure 3. Once instantiated, Privilege objects are used as shown here:

Privilege debugPrivilege = new Privilege(Privilege.Debug); try { debugPrivilege.Enable(); // perform tasks requiring the privilege (the payload) ... } catch { debugPrivilege.Revert(); throw; } finally { debugPrivilege.Revert(); }

The call to the Revert method is placed inside the finally block as a precaution to remember to revert the privilege despite any exception that might be thrown by the code following the Enable method. Forgetting to revert a privilege is a serious programming error. Any call to Enable or Disable must be followed by a call to Revert. The design makes it illegal to call Disable immediately after Enable, or vice versa. A call to Revert can be skipped only if the NeedToRevert property returns false. For reasons that will become apparent later, methods of the Privilege class must be invoked on the same thread that the Privilege object was created on.

Figure 3 Public Interface of the Privilege Class

public delegate void PrivilegedCallback(object state); [ComVisible(false)] public sealed class Privilege { public const string Debug = "SeDebugPrivilege"; public const string Security = "SeSecurityPrivilege"; public const string TakeOwnership = "SeTakeOwnershipPrivilege"; ... // continue with constants for all known privileges public Privilege(string name) public void Enable() public void Disable() public void Revert() public bool NeedToRevert { get; } public static void RunWithPrivilege( string privilege, bool enabled, PrivilegedCallback callback, object state) }

A careful reader will have noticed another call to Revert inside a catch block. The reason for this is to avoid a security vulnerability known as a luring attack. If the payload throws an exception, the exception is going to propagate up the callstack twice—once in search of an exception filter, and the second with an actual stack unwind. This is the same two-pass exception handling mechanism that Win32® structured exception handling (SEH) follows. You can't write a user exception filter in C# today, but you can in Visual Basic®, Visual C++®, or in plain Microsoft intermediate language (MSIL). The code inside the exception filter could then run with elevated privilege, which is not intended behavior and can be damaging in certain situations. This is why a very broad catch block is inserted after the try block. This catch block is guaranteed to catch all exceptions, stopping the luring attack. Inside this catch block, I revert the privilege, and rethrow the exception.

In my implementation, all calls to Windows APIs are wrapped by an internal class called NativeMethods. You will see references to this class in subsequent code snippets.

In order to implement the functionality correctly, you must face a number of challenges, most of which are unique to managed code programming. I'll describe these challenges and their solutions in the following sections.

Challenge 1: Living Inside Application Domains

The common language runtime (CLR) uses application domains to provide isolation between applications. You can find an excellent overview of application domains at Using Application Domains, so I won't repeat it here. Suffice it to say that extreme care must be taken to maintain this isolation. Operations that an application performs should in no way affect the operation of other applications running inside its sister application domains.

This is therefore the first hurdle you must overcome in order to manipulate privileges safely in managed code. In unmanaged code applications, even multithreaded ones, it is sometimes okay to enable the privilege on the process token, thereby affecting all threads inside the process. For instance, a backup application might enable the Backup privilege globally, so all of its threads can then open files regardless of their ACLs. This approach clearly does not work for some managed apps. Multiple application domains can be hosted inside a single Windows process, so whatever happens to the process token affects every application domain inside that process. Of course, this isn't unique to managed applications. When developing native applications, you wouldn't tamper with the process state if you suspected that your application could be co-hosted with other applications inside the same Windows NT process, as is, for example, possible with Windows NT services.

The Privilege class takes several precautions in order to be a good app domain citizen. One such precaution limits privilege manipulation to the thread on which the privilege is required. The privilege code remembers the thread on which the object was constructed, and forces all subsequent operations to take place on the same thread. The Thread.CurrentThread property is helpful in this instance. This is why when you read the code, you will see a private member defined as:

private readonly Thread currentThread = Thread.CurrentThread;

All subsequent attempts to call methods of a Privilege object begin with a check against this cached thread instance:

// All privilege operations must take place on the same thread if (!this.currentThread.Equals(Thread.CurrentThread)) { throw new InvalidOperationException( "This operation must be performed on the same " + "thread that created this instance."); }

As an aside, this mechanism works even if there is a user-mode scheduler in place, and if scheduling takes place on fibers instead of threads. Thread ID equality will continue to work even if your code is preempted and resumed on a different fiber.

The sequence of steps that lets you avoid modifying the security token of the hosting process has been outlined in the previous section. Figure 4 shows what it looks like in managed code (several details were stripped or modified in this sample; see the full solution in the code download for complete implementation).

Figure 4 Maintaining Application Domain Isolation

bool success = true; if (false == NativeMethods.OpenThreadToken( NativeMethods.GetCurrentThread(), TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges, true, ref this.threadHandle )) { error = Marshal.GetLastWin32Error(); if (error != NativeMethods.ERROR_NO_TOKEN) { success = false; } if (success == true) { error = 0; if (false == NativeMethods.OpenProcessToken( NativeMethods.GetCurrentProcess(), TokenAccessLevels.Duplicate, ref processHandle)) { error = Marshal.GetLastWin32Error(); success = false; } } if (success == true) { if (false == NativeMethods.DuplicateTokenEx( processHandle, TokenAccessLevels.Impersonate | TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges, IntPtr.Zero, SecurityImpersonationLevel.Impersonation, TokenType.Impersonation, ref this.threadHandle)) { error = Marshal.GetLastWin32Error(); success = false; } } if (success == true) { if (false == NativeMethods.SetThreadToken( IntPtr.Zero, this.threadHandle)) { error = Marshal.GetLastWin32Error(); success = false; } } if (success == true) { this.isImpersonating = true; } }

When the time comes to revert the privilege, one of two code paths is taken, depending on the initial state of the thread at the time of creation. If the thread was impersonating to begin with, the privilege can be reverted by a call to AdjustTokenPrivileges, passing what was originally returned as the initial state of the privilege as its new state. If the thread was not impersonating when the privilege was enabled, the privilege can be reverted to its initial state (that of defaulting its security token to the process) by simply removing the thread token. This can be accomplished by calling RevertToSelf. In this particular case, calling AdjustTokenPrivileges prior to RevertToSelf is a legal, but unnecessary, step.

In either case, the process token remains unaffected by the manipulations you perform, so sister application domains will maintain their proper degree of isolation.

Challenge 2: Changing Multiple Privileges

An easy caveat to overlook when designing privilege manipulation code is the possibility that the state of multiple privilege objects will be manipulated on the same thread at the same time. For instance, if you want to change an owner and a system access control list (SACL) on an object, you'll want to open the file handle for both WRITE_OWNER and ACCESS_SYSTEM_SECURITY access. In order to do this, the caller may have to enable both Take Ownership and Security privilege at the same time. The algorithm developed so far looks like it handles it just fine (here you assume that the thread is not impersonating to begin with).

Let's start with enabling Privilege A. You must first impersonate, enable the privilege, and remember the previous state of the privilege (A-before) so that the privilege can later be reverted. Now that the thread is already impersonating, to enable Privilege B, you simply enable the privilege and remember the previous state of the privilege (B-before).

After all operations have been performed with the privileges enabled, they now need to be reverted. To revert Privilege B, revert the privilege to B-before, but do not revert to self, since the thread was already impersonating before Privilege B was enabled. To disable Privilege A, there is no need to revert the privilege, since reverting to self will take any changes to the thread token with it, including changes made to privileges.

The callers of the Privilege class cannot be reasonably expected to always properly nest their privilege manipulations as described. A caller can make a tiny mistake, or have a legitimate need, leading them to revert Privilege A before Privilege B. If this happens, the logic unravels. When Privilege A is reverted, the reversion to self will cause all changes to the thread token to be lost, including those for Privilege B.

Clearly, the algorithm needs tweaking. Multiple instances of the Privilege class should all cooperate, as long as they are all instantiated on the same thread. It should not matter whether they are instantiated within the same function call, or in different places in the same callstack. A facility that comes in handy in this case is known to Windows system developers as Thread Local Storage (TLS), also known to managed code programmers as Local Data Store (LDS). TLS is data that is available exclusively to all code running inside a particular thread. Under this arrangement, every thread has its own value of the data. This is different from having global or static data, where all threads share the same value. TLS is going to be helpful in this instance because you can use it to keep track of the state of privilege manipulation on a given thread.

My implementation introduces a private class inside the Privilege class called TlsContents. Instances of this class are disposable, reference-counted wrappers around a token handle, which keep track of whether the token handle was placed on the thread via impersonation. The internal workings of this class are worth discussing, in particular the manner in which the enclosing Privilege class makes use of it. The technique boils down to this: for as long as any privileges are being manipulated on a thread, there is going to be a local data store slot allocated with a TlsContents instance inside of it. This instance is going to be reference-counted with as many references as there are privileges being changed on the thread.

Before a privilege can be toggled on a thread, a TlsContents instance is created and a reference to it is placed inside the local data store slot inside that thread. The TlsContents constructor performs the necessary steps to ensure that the thread is impersonating by executing the code in Figure 4. It is not until all privileges are reverted that the TlsContents instance is disposed, taking away the thread token with it, if appropriate.

Armed with this functionality, the privileges can now be enabled and reverted in any succession. Before a privilege can be toggled, you must make use of TLS, as shown in Figure 5.

Figure 5 Making Use of Thread Local Storage

// Private static member that contains the local data store slot private static LocalDataStoreSlot tlsSlot = Thread.AllocateDataSlot(); // Retrieve TLS state this.tlsContents = Thread.GetData( tlsSlot ) as TlsContents; if (this.tlsContents == null) { // no privileges touched yet on this thread this.tlsContents = new TlsContents(); Thread.SetData(tlsSlot, this.tlsContents); } else { // increment the reference count of the existing instance this.tlsContents.IncrementReferenceCount(); }

The TokenHandle member of the TlsContents instance can, at any point, be used to reference the thread token, as is illustrated in the following call:

NativeMethods.AdjustTokenPrivileges( this.tlsContents.TokenHandle, false, ref newState, (uint)Marshal.SizeOf(previousState), ref previousState, ref previousSize)

When the time comes to revert the privilege, reference counting is used to determine whether it is time to revert from impersonation:

if (this.tlsContents != null) { if (0 == this.tlsContents.DecrementReferenceCount()) { this.tlsContents = null; Thread.SetData(tlsSlot, null); } }

The DecrementReferenceCount method will do the necessary cleanup work when the reference count reaches 0; the caller needs to simply null out the thread local store value.

Challenge 3: Performance

You should keep in mind that privilege manipulation is a very expensive process. In fact, doing it too frequently can adversely affect the performance of the entire application, so in some cases it makes sense to have a thread that runs with the privilege enabled, and enqueue the tasks that require the privilege to that thread. It is instructive to go through a series of steps involved in enabling a privilege. To begin, let's consider the most computationally significant of steps involved: kernel transitions. These are API calls that perform their work by switching from user mode to kernel mode and back. Kernel transitions are so expensive (requiring thousands upon thousands of instructions) that they completely dwarf all other processing of the Privilege class. Ultimately, how fast a privilege can be enabled depends on little else but the number of kernel transitions involved.

For instance, one of the early attempts at the Privilege class implementation achieved impersonation by making the following sequence of Windows API calls: OpenThreadToken, ImpersonateSelf, OpenThreadToken, and AdjustTokenPrivileges. Ideally, the operation could be completed simply by opening the thread token handle and then adjusting the token privileges, resulting in two transitions. However, as noted earlier, this process will usually fail. As such, after opening the thread token, the ImpersonateSelf Windows API was used. This function first opens the process token handle, duplicates the process token, and then sets the copy of the handle on the thread. It then closes the process token and closes the thread token. This sequence results in an additional five transitions, bringing the total to eight transitions for the entire operation.

This number can be improved upon by addressing some inefficiencies. The first inefficiency comes from opening (and then closing) the process token every time a privilege is toggled. The process security token is not going to change and can be cached. The second inefficiency comes from ImpersonateSelf, which sets a token on a thread, just to close it. After ImpersonateSelf returns, the thread token needs to be opened again (at a cost of an extra kernel transition). The optimized solution opens the process token once and then caches it, as shown in the code in Figure 6.

Figure 6 Caching the Process Token

static IntPtr processHandle = IntPtr.Zero; static readonly object syncRoot = new object(); ... if (processHandle == IntPtr.Zero) { lock (syncRoot) { if (processHandle == IntPtr.Zero) { if (false == NativeMethods.OpenProcessToken( NativeMethods.GetCurrentProcess(), TokenAccessLevels.Duplicate, ref processHandle)) { error = Marshal.GetLastWin32Error(); success = false; } } } }

The "check, lock, check again" code (known as a "double-checked lock") you see in Figure 6 is a standard implementation pattern for thread-safe setting of write-once static or global variables. (Note that this pattern is unsafe in unmanaged C++ due to the lack of a strong memory model in the language. The CLR uses a strong memory model on all platforms, so this pattern is safe.) Having the process handle cached makes all subsequent privilege operations cheaper by two kernel transitions, saving one open and one close call—clearly a good thing.

With the process handle in hand, the rest of the impersonation operation can be optimized by opening a thread token (as mentioned already, this will likely fail, but you have to make sure), duplicating the process token with DuplicateTokenEx, and setting the duplicate token on the thread with SetThreadToken.

An experienced system programmer will have noticed that DuplicateTokenEx was called instead of DuplicateToken. The reason for this stems from the fact that the duplicated handle will be used for two purposes—impersonation (setting it on the thread) and adjusting the token privileges by calling the AdjustTokenPrivileges API. DuplicateTokenEx is an API that allows you to specify the rights with which the handle is to be opened. This is different from DuplicateToken, which defaults to Query and Impersonate rights only.

The new sequence of API calls, after caching the process token, takes only four kernel transitions (open, duplicate, set, adjust) instead of the original eight, so it is going to be almost twice as fast. Not a bad payback for a little attention to kernel transitions!

For the revert operation, the steps (assuming revert to self without readjusting the privileges) involve calling RevertToSelf (amounts to setting a NULL token handle on the current thread) and closing the handle. As you can see, revert is cheaper, requiring only two operations, which could have been three if you didn't understand what you were doing by calling AdjustTokenPrivileges prior to RevertToSelf.

This is not the end of our bag of performance optimizing tricks, however. Another, simpler performance gain comes from the mapping of privilege names to the corresponding LUIDs. The privilege class internally maintains a static mapping of privilege names to their corresponding LUIDs. Since LUID values corresponding to each privilege name remain the same for as long as a system is up, the LUIDs can be cached in order to improve the performance of subsequent lookups. This caching can result in a noticeable performance improvement because each call to the LookupPrivilegeValue API results in a Local Procedure Call (LPC) into the Local Security Authority process—a cost best avoided. The Privilege class uses a HybridDictionary data structure for doing this job. This data structure has exactly the same public interface as the Hashtable class, but with significantly better performance characteristics when dealing with small data sets. The implementation of this logic comes down to nothing more than performing locking and lookup correctly. A slightly abbreviated version of this logic is illustrated by the code in Figure 7.

Figure 7 Caching Privilege LUIDs

private static HybridDictionary privileges = new HybridDictionary(); private static HybridDictionary luids = new HybridDictionary(); private static ReaderWriterLock privilegeLock = new ReaderWriterLock(); private static NativeMethods.LUID LuidFromPrivilege(string privilege) { NativeMethods.LUID luid; // Look up the privilege LUID inside the cache try { privilegeLock.AcquireReaderLock(Timeout.Infinite); if (luids.Contains(privilege)) { luid = (NativeMethods.LUID)luids[privilege]; privilegeLock.ReleaseReaderLock(); } else { // LUID not found! Look it up, but do it outside of any lock. privilegeLock.ReleaseReaderLock(); luid.LowPart = 0; luid.HighPart = 0; if (false == NativeMethods.LookupPrivilegeValue( null, privilege, ref luid)) { int error = Marshal.GetLastWin32Error(); ... // throw appropriate exception based on the error // code } privilegeLock.AcquireWriterLock(Timeout.Infinite); } } finally { if (privilegeLock.IsReaderLockHeld) { privilegeLock.ReleaseReaderLock(); } if (privilegeLock.IsWriterLockHeld) { // check the cache again! After all, we are in // a multithreaded world and things could have changed if (!luids.Contains(privilege)) { luids[privilege] = luid; // map both ways to enable better error reporting privileges[luid] = privilege; } privilegeLock.ReleaseWriterLock(); } } return luid; }

A careful reader will have noticed something strange about how the cache is updated: you obtain the writer lock inside the try block, yet the cache is updated inside the finally block. The reasons for this will be explained in a later section dealing with writing reliable code. When using the .NET Framework 1.1, this trick is not really necessary, but the code stays this way to make it as close as possible to the version written for version 2.0 of the Framework.

This completes the version of the Privilege class tailored to the .NET Framework 1.1. The remainder of this article deals with features available only in .NET Framework 2.0. Even if you are developing for the .NET Framework 1.x, you should continue reading. There are some features in the .NET Framework 2.0 that provide guarantees for writing deterministic, reliable code that .NET Framework 1.x cannot possibly match. If you discover that those guarantees are important to your code, you may have to postpone developing Privilege code until .NET Framework 2.0 is available.

Challenge 4: Deterministic Execution

This section deals with a very thorny aspect of managed code programming—writing reliable code. Authoring reliable code can be a daunting task. There is an entire class of "exceptional" conditions that developers generally do not expect in the normal code execution path. These exceptional conditions include thread aborts, out of memory conditions, and stack overflow exceptions (unmanaged code has equivalents to many of these problems; stack overflows and allocation failures aren't unique to managed development and are difficult to deal with correctly). In managed code, these conditions generate what are usually called asynchronous or out of band exceptions. How can the code make any guarantees regarding its behavior under those types of conditions?

It turns out that making the privilege manipulation logic deterministic, even in the face of those catastrophic exceptions, is very important. You have seen thus far that the process of manipulating privileges is something that is done in several computationally expensive and error-prone steps. This sequence of steps, once initiated, must run to completion. Failure to do so can leave the thread in an inconsistent state (like impersonating when it shouldn't be). This sort of challenge could not be addressed using facilities available in the .NET Framework 1.x. Fortunately, there are new features of the .NET Framework 2.0 called Reliability Contracts and Constrained Execution Regions and they are going to be helpful to the Privilege class.

What follows is a brief introduction to constrained execution regions (CERs), focusing on the subset of CER functionality that the Privilege class uses. Within the .NET Framework 2.0 is ReliabilityContractAttribute, a custom attribute that defines a contract specifying the reliability guarantees that a method makes in the face of exceptional conditions. In essence, this attribute enables reliability guarantees to be both documented and discoverable via a programming model. Constrained execution regions help implement code that satisfies the contract specified by the ReliabilityContractAttribute by moving CLR-induced failures to either before or after a block of code that executes under some constraints. CLR-induced failures include running out of memory while JITing code, as well as aborting a thread. The reliability contract can be stated in one of several ways, as shown in Figure 8.

Figure 8 Reliability Contract

In order to declare that Use the attribute
A method can potentially corrupt all state (instance, process, and so on) in the face of exceptional conditions [ReliabilityContract(Consistency.MayCorruptProcess, Cer.None)]
A method can potentially corrupt shared state within the app domain (for example, static variables) in the face of exceptional conditions [ReliabilityContract(Consistency.MayCorruptAppDomain, Cer.None)]
A method can potentially corrupt instance state in the face of exceptional conditions [ReliabilityContract(Consistency.MayCorruptInstance, Cer.None)]
A method will not corrupt state but may fail in the face of exceptional conditions [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
A method will not corrupt state and will not fail even in the face of exceptional conditions (modulo failures due to invalid arguments) [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]

How are these guarantees implemented? In the first three cases, the developer does nothing special to the code—he inspects the code and simply documents the behavior should exceptional conditions occur. If state transitions in a method affect just the instance, the developer should go with option three; if manipulations are more extensive, options one or two are best.

Methods of the privilege class may fail for a variety of reasons (the caller may not possess the privilege, and there could be a variety of other operating-system level failures). They should not, however, have the luxury of corrupting the system state. Once a privilege manipulation is initiated, the following three guarantees must be made:

  • Either a privilege will be placed in its desired state, or it won't. No legal intermediate states exist.
  • In case a privilege is placed in its desired state, the instance of the Privilege class must be placed in a consistent state that would allow a Revert operation to be executed.
  • If something happens along the way and the privilege can not be toggled, the thread token and the Privilege instance must be reverted exactly to the state in which the operation was initiated. Failure to do so will leave the thread in an inconsistent state—a problem that is more severe than corrupting just a Privilege instance.

Thus, the correct reliability contract for Privilege methods is the same as the fourth option in Figure 8, using a ReliabilityContractAttribute with Consistency.WillNotCorruptState and Cer.MayFail. You will see several methods in the .NET Framework 2.0 version of the Privilege class decorated in this manner.

So much for decorating methods. How about actually implementing the guarantees stated by the reliability contract? This is where constrained execution region functionality comes into play. The code is made reliable in the face of asynchronous exceptions by instructing the runtime to perform a sequence of steps known internally as "eagerly preparing" the compiled code.

This means that the CLR has already done all the work necessary to run your code. As an example, a method may need to be just-in-time (JIT)-compiled and may call a method in another assembly. This may require loading another assembly, running a few class constructors, then compiling your callee as well as your calling method. Eagerly preparing code ensures that the failures the CLR may introduce to your code are hoisted to the point at which you do the eager preparation. Asynchronous exceptions are then delayed over catch and finally blocks. This guarantees that catch and finally blocks will execute to completion in case of asynchronous system exceptions. All this is achieved via the implementation pattern (see Figure 9).

Figure 9 Programming with Constrained Execution Regions

using System.Runtime.CompilerServices; [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] public void SomeMethod() { RuntimeHelpers.PrepareConstrainedRegions(); try { // Implementation goes here } catch( /* any necessary catch blocks */ ) { } finally { // put in the back-out code here to ensure consistency // the code is guaranteed to run, ensuring reliability } }

Before the try block is entered, a special method, RuntimeHelpers.PrepareConstrainedRegions, is invoked. This instructs the runtime to perform any operations necessary to ensure that the following catch and finally blocks will be allowed to execute. Pretty simple for such a complex piece of functionality, isn't it? It gets more interesting still. In cases in which the code undertakes a nontrivial sequence of steps, making the back-out code too complex, the trick that is shown in Figure 10 can be employed.

Figure 10 Making Code Uninterruptible

RuntimeHelpers.PrepareConstrainedRegions(); try { // The payload is entirely in the finally block. // This is how we ensure that the code will not be // interrupted by catastrophic exceptions. } finally { try { // payload goes here } catch( /* any necessary catch blocks */ ) { } finally { } }

The arrangement in Figure 10, strange as it may look, is exactly what you will see inside the version of the Privilege class that works within the .NET Framework 2.0. And now you know why!

Challenge 5: Safe Encapsulation of Windows NT Handles

Constrained execution regions are used extensively in the .NET Framework 2.0. One very common area where they are applied is in dealing with handle objects. In the case of the Privilege class, let's turn our attention to token handles.

Token handles can be tricky to work with correctly. In the .NET Framework 1.x, all handle values were manipulated as IntPtr objects (essentially, thinly wrapped numerical values). This approach is inherently unsafe and insecure for a variety of reasons, which led to the development of the SafeHandle family of classes in the .NET Framework 2.0.

The SafeHandle class in the .NET Framework 2.0 was designed with one important goal in mind—to make sure that the CLR never leaks an operating system handle. These handle objects are created in a noninterruptible way and released even in catastrophic failure scenarios. They can be passed to unmanaged code. They are also reference counted to prevent security problems related to a phenomenon known as handle recycling. The SafeHandle class is therefore very important. The CLR team believes that without this functionality, it is impossible to write reliable managed code.

In the .NET Framework 1.1, handles were opened using a line of code that looks something like this:

IntPtr myFileHandle = NativeMethods.FindFirstFile(fullPath, data);

This line will leak a file handle if, for example, a ThreadAbortException is thrown before the handle is stored in the myFileHandle local variable. Additionally, even if the value was stored in a managed object, there would be no guarantee that the finalizer on that object would run. SafeHandle guarantees finalization and is safe in the face of catastrophic exceptions. The interop layer in the .NET Framework 2.0 recognizes P/Invoke methods that return SafeHandle subclasses (as return values or as an out parameter) as a special case of P/Invoke and takes the appropriate action to ensure the handle's identifier is wrapped successfully in the SafeHandle object.

Special handling of SafeHandle subclasses does not end there. Every time a SafeHandle instance is passed to the unmanaged side, the interop layer will increment its reference count. When the call is completed, the ref count will be decremented. This reference count is separate from the lifetime of the object in the garbage collection (GC) heap, but instead refers to the number of threads that may be currently using a SafeHandle.

This reference-counting feature was introduced to eliminate a security problem related to handle reuse (also known as handle recycling). Consider this scenario: two threads are executing the same semi-trusted code that accesses some publicly available file. One thread gets an OS handle with value x. Before any read is performed, this thread is context switched out. The second thread gets the same handle, performs some read, and then closes the file. Without ref counting, when the file is closed, handle is considered invalid and available for recycling. The third thread that executes fully trusted code opens some private file, gets a handle back from the OS, and this handle has the same value x but now points to different file. When the first thread is started again, it will have an access to something it shouldn't. Since SafeHandle subclasses are ref counted when passed to unmanaged code, they won't be released back to the OS via a CloseHandle-like method until their ref count reaches zero.

The Privilege class wraps the Windows NT token handles with the SafeTokenHandle class illustrated in Figure 11. This class derives from SafeHandleZeroOrMinusOneIsInvalid, a lengthy but descriptive name of a SafeHandle subclass that parallels the behavior of Windows NT handles (whose invalid values are 0 and -1). This implementation is boilerplate and you can use it in all instances that call for the creation of SafeHandle subclasses. The only significant difference between different types of handles is the name of the API that has to be called in order to close the handle (in this case, CloseHandle is called).

Figure 11 SafeTokenHandle Class

internal sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid { private SafeTokenHandle() : base (true) {} internal SafeTokenHandle(IntPtr handle) : base (true) { SetHandle(handle); } internal static SafeTokenHandle InvalidHandle { get { return new SafeTokenHandle(IntPtr.Zero); } } [DllImport("kernel32", SetLastError=true), SuppressUnmanagedCodeSecurity, ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] private static extern bool CloseHandle(IntPtr handle); protected override bool ReleaseHandle() { return CloseHandle(handle); } }

The .NET Framework 2.0 version of the Privilege class uses SafeTokenHandle in every place where the .NET Framework 1.1 version uses IntPtr. You will see this if you examine the code. Switching to safe handles is really that simple.

Improving the Privilege Class

The Privilege class has one additional feature specifically for invoking privileged operations in a correct and safe manner. This is achieved via a special PrivilegedCallback delegate and a corresponding RunWithPrivilege method. The method is illustrated in the code in Figure 12.

Figure 12 Using Privileges with Delegates

public delegate void PrivilegedCallback(object state); public static void RunWithPrivilege( string privilege, bool enabled, PrivilegedCallback callback, object state) { if (callback == null) throw new ArgumentNullException("callback"); Privilege p = new Privilege(privilege); RuntimeHelpers.PrepareConstrainedRegions(); try { if (enabled) p.Enable(); else p.Disable(); callback(state); } catch { p.Revert(); throw; } finally { p.Revert(); } }

As you can see, the RunWithPrivilege method does all the necessary setup work by creating a privilege instance, preparing a constrained region, and structuring the code in a way that enables or disables the privilege as necessary, and then invoking the delegate. All the while, privilege reversal is assured, and luring attacks are prevented by strategic placement of the catch block. If you do not use RunWithPrivilege, make sure that the code that invokes privileged operations is structured in exactly the same way.

As you can see, privilege tweaking is not for the faint of heart. The logic necessary to do it correctly touches on a number of advanced managed code and system programming techniques, such as constrained execution regions and safe handles. Some of the tricks necessary to protect your code are not available until the .NET Framework 2.0.

Like most software, though, my implementation is not perfect. For instance, CLR hosting processes other than Windows may find operations on thread tokens offensive and prevent them through the very low-level CLR hosting interfaces. Under such hosting environments, calls like ImpersonateSelf or SetThreadToken have no effect and the thread token is not changed. This restriction can be circumvented by calling methods in the WindowsIdentity class, but that is beyond the scope of this article.

Another great improvement to my implementation would be to supplement it with a dedicated code access security (CAS) permission. As coded, the Privilege class requires unmanaged code permissions in order to execute because of its heavy reliance on the Windows API. Decorating this class with demands for a dedicated TogglePrivilege permission would allow you to create a version of the class that can live inside "allow partially trusted callers" assemblies. Ideally, such CAS permission would have the granularity of individual permission names, rather than a blanket permission to enable anything in the caller token's process.

All in all, however, this code is likely to save you countless hours of design, implementation, and debugging work. The full implementation for both the .NET Framework 1.x and the .NET Framework 2.0 are available for download from the MSDN Magazine Web site. In the meantime, it would be a good idea for you to review your existing codebase for calls to the AdjustTokenPrivileges API in order to make sure all the pitfalls that I discussed in this article have been handled.

Mark Novak is a Security Development Lead at Microsoft. Mark has been with Microsoft since 1995. After contributing to several releases of Microsoft Exchange Server, he has spent the last five years concentrating on software security. This is his second article for MSDN Magazine. Mark can be contacted at markpu@microsoft.com.