SafeHandle

Prior to Whidbey, interop with Win32 handles was done by passing IntPtrs back and forth through P/Invoke.  This had several drawbacks including:

  • Lack of type safety.  Nothing is preventing me from taking an IntPtr containing a HWND and passing it to a method expecting an IntPtr containing an HCERTSTORE
  • No protection against handle recycling attacks
  • No guaranteed cleanup

Whidbey introduces the SafeHandle class, which can be used to wrap around handles to unmanaged resources.  SafeHandle helps to solve these problems and quite a few more.  For more background on the problems SafeHandle solves and how it goes about solving them, look at the SafeHandle section in Chris Brumme's Finalization blog entry.

SafeHandle is an abstract class, which you derive from to create a wrapper around specific types of handles. (Or other unmanaged resources that require some sort of cleanup).  The CLR will then work with P/Invoke to marshal your safe handle back and forth to managed code.  One of the more important features of SafeHandles is that it contains a critical finalizer.  In brief, this means that the cleanup code will be:

  • JITed ahead of time (meaning that out of memory conditions will not prevent a handle's cleanup code from going through the JIT and then executed)
  • Executed in a way that thread aborts will not interrupt the cleanup code
  • Can only call other code that can be eagerly prepared

Although the CLR puts in a lot of effort to ensure that it doesn't inject an error that would cause your cleanup code to fail to execute, you have to be sure that you don't call anything that would inject a failure.  For this reason, any P/Invoke calls you make when releasing the resources that the SafeHandle wraps around should be done with SuppressUnmanagedCodePermission, and you should ensure that you only call other methods that are guaranteed safe to run.  Generally this means that those methods also have been marked with the ReliabilityContractAttribute for Consistency.WillNotCorruptState (if an exception occurs, the method will not corrupt object instance, AppDomain, or Process state) and CER.Success (even when exceptions occur, the method will always succeed in a constrained execution region) or CER.MayFail (the method may fail in exceptional circumstances, however it will report this failure back to the CLR).

Your subclass of SafeHandle is only required to provide three methods

  • .ctor() - A default constructor that initializes the SafeHandle.  This method is used by P/Invoke when it returns a SafeHandle to your process
  • bool IsInvalid { get; } - a property to determine if the current value of the handle is valid or not
  • bool ReleaseHandle() - clean up the contained resource

For instance, I might create a SafeHandle wrapper around a handle to a certificate store (HCERTSTORE).  In order to accomplish this, first I would create a new CertificateStoreHandle class, and derive it from the SafeHandle base class.  I'd also create the default constructor:

public sealed class CertificateStoreHandle : SafeHandle
{
    private CertificateStoreHandle() : base(IntPtr.Zero, true)
    {
        return;
    }    

The first argument to the SafeHandle constructor is the value that should be used to represent an invalid handle.  The second parameter will generally always be true, it tells SafeHandle that it owns the resource it contains.  If this value were to be set to false, then ReleaseHandle would not be called, and you might end up leaking resources.  Also note that the constructor is private not public.  This allows P/Invoke to create instances of your class, but other managed code cannot do the same.

The IsInvalid property is easily implemented, since all I need to do is check to see if the handle is zero.

public override bool IsInvalid
{
    get { return handle == IntPtr.Zero; }
}

Finally, ReleaseHandle will call CertCloseStore in order to close the HCERTSTORE.  Notice how CertCloseStore is decorated with SuppressUnmanagedCodeSecurity so that I'm guaranteed the security system will not inject an error path into the cleanup code.  Additionally CertCloseStore must have a reliability contract applied, since it is part of the call graph of SafeHandle's finalizer (via ReleaseHandle), and the SafeHandle finalizer will run in a CER.

[SuppressUnmanagedCodeSecurity]
[ReliabilityContract(Consistency.WillNotCorruptState, CER.Success)]
[DllImport("Crypt32.dll", CallingConvention = CallingConvention.StdCall)]
private static extern bool CertCloseStore(IntPtr hCertStore, int dwFlags);

protected override bool ReleaseHandle()
{
    return CertCloseStore(handle, 0);
}

That's all there is to it. 20 lines of code just bought you all the benefits listed above and in Chris' blog entry.  Other APIs that use certificate stores would now be declared as taking CertificateStoreHandle parameters instead of IntPtrs, for instance the P/Invoke declaration for CertDuplicateStore would now be:

[DllImport("Crypt32.dll", CallingConvention = CallingConvention.StdCall)]
private static extern CertificateStoreHandle CertDuplicateStore(CertificateStoreHandle hCertStore);