This has been a busy month for blogging for me, I’m up to a whopping 8 posts this month including this one which is the most I have written in any given month (since Feb 2006).
We have seen a few cases lately where ASP.NET apps die due to an unhandled
CryptographicException when finalizing a SafeHandle. Here is the explanation of why this happens and what you can do to avoid it.
Intermittently ASP.NET will crash with the following entries in the appication event log
Event Type: Error Event Source: ASP.NET 2.0.50727.0 Event Category: None Event ID: 1334 Date: 2007-10-31 Time: 08:10:03 User: N/A Computer: PRATHER Description: An unhandled exception occurred and the process was terminated. Application ID: /LM/w3svc/1/ROOT/TestCrypto Process ID: 4296 Exception: System.Security.Cryptography.CryptographicException Message: Keyset does not exist StackTrace: at System.Security.Cryptography.CryptographicException.ThrowCryptogaphicException(Int32 hr) at System.Security.Cryptography.SafeProvHandle._FreeCSP(IntPtr pProvCtx) at System.Security.Cryptography.SafeProvHandle.ReleaseHandle() at System.Runtime.InteropServices.SafeHandle.InternalFinalize() at System.Runtime.InteropServices.SafeHandle.Dispose(Boolean disposing) at System.Runtime.InteropServices.SafeHandle.Finalize() For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.
followed by this event
Event Type: Error Event Source: .NET Runtime 2.0 Error Reporting Event Category: None Event ID: 5000 Date: 2007-10-31 Time: 08:10:03 User: N/A Computer: PRATHER Description: EventType clr20r3, P1 w3wp.exe, P2 6.0.3790.3959, P3 45d6968e, P4 mscorlib, P5 22.214.171.124, P6 46693664, P7 4d78, P8 6, P9 udta330idobh2roz2ayvlcelag5agtls, P10 NIL. For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.
accompanied by this event in the system log
Event Type: Warning Event Source: W3SVC Event Category: None Event ID: 1009 Date: 2007-10-31 Time: 08:10:22 User: N/A Computer: PRATHER Description: A process serving application pool 'DefaultAppPool' terminated unexpectedly. The process id was '4296'. The process exit code was '0xe0434f4d'. For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.
So we know that we have an unhandled
SafeProvHandle._FreeCSP while finalizing a SafeHandle, but we don’t know why. On a related note, the reason the unhandled exception on the finalizer causes a process crash, and how to debug unhandled exceptions in general is described here.
It should also be noted that the call stacks for the same issue can be cryptographic exceptions in any of
SafeHandle._FreeHKey depending on the situation.
A problem like this is pretty hard to track down because the correlation between cause and effect is not very obvious.
This problem only occurs if you create
RSACryptoServiceProviders while impersonated and don’t clean up the keys of the
RSACryptoProvider using the clear method while impersonated.
CspParameters cspParam = new CspParameters(); cspParam.Flags = CspProviderFlags.UseMachineKeyStore; RSACryptoServiceProvider pair = new RSACryptoServiceProvider(cspParam); string keyInfo = pair.ToXmlString(false); //... encrypt or decrypt data
What actually happens is this
- Impersonate user (either manually or through impersonate=true in web.config)
- Create a new RSACryptoServiceProvider with an Ephemeral Key like UseMachineKeyStore
- Undo impersonation (i.e. finish the request) without cleaning up the key
- The Key gets cleaned up when the finalizer runs under the process account
The problem here is that since the key is created while impersonated so it is created under the impersonated users profile. When the finalizer runs it does not run under the impersonated user so the key does not exist and you get an exception like “keyset does not exist”, and since there is no one there to handle it the process exits out. Alternatively you could get an exception to the effect that the finalizer does not have permissions to delete a key or similar.
If you use a named key-pair instead of the ephemeral keys, i.e. create a new CspParameter with an arbitrary KeyContainerName the problem will not occur since the framework will not attempt to delete this when disposing of the
To see the problem in action, put the above code in an aspx page with
impersonate=true or surrounded by
System.Security.Principal.WindowsImpersonationContext impersonationContext; impersonationContext = ((System.Security.Principal.WindowsIdentity)User.Identity).Impersonate(); ... impersonationContext.Undo();
GC.Collect() in another aspx page.
The solution to the problem is to follow these steps
- Impersonate user
- Create a new RSACryptoServiceProvider (pair)
- Encrypt or Decrypt your data
- Call clear to release the keys (pair.Clear())
- Undo impersonation
Whenever we are dealing with objects that hold on to native resources we should follow the finalization/disposal rules in order to avoid higher than necessary memory usage and resource depletion, but in this case, not following the rules of disposing finalizable objects directly after use (by calling Close, Dispose or Clear, whichever the dispose method happens to be) causes even worse problems than just resource depletion.