4

I'm getting an MDA after running this code for the second time in a loop (with a different file parameter:

byte[] encryptedData = File.ReadAllBytes(file); // before this line it throws, see exception below
long dataOffset;

using (var stream = new MemoryStream(encryptedData))
using (var reader = new BinaryReader(stream))
{
    // ... read header information which is not encrypted
}


using (var stream = new MemoryStream(encryptedData))
{
    stream.Seek(dataOffset, SeekOrigin.Begin);

    using (var aesAlg = new AesCryptoServiceProvider())
    using (var decryptor = aesAlg.CreateDecryptor(key, iv))
    using (var csDecrypt = new CryptoStream(stream, decryptor, CryptoStreamMode.Read))
    using (var reader = new BinaryReader(csDecrypt))
    {
        decrypted = reader.ReadBytes((int)(encryptedData.Length - dataOffset));
    }
}

The MDA is the following:

A SafeHandle or CriticalHandle of type 'Microsoft.Win32.SafeHandles.SafeCapiKeyHandle' failed to properly release the handle with value 0x000000001BEA9B50. This usually indicates that the handle was released incorrectly via another means (such as extracting the handle using DangerousGetHandle and closing it directly or building another SafeHandle around it.)

The stacktrace is not too informative:

mscorlib.dll!System.Runtime.InteropServices.SafeHandle.Dispose(bool disposing) + 0x10 bytes mscorlib.dll!System.Runtime.InteropServices.SafeHandle.Finalize() + 0x1a bytes

I suspect that one of the streams or the CryptoServiceProvider is not released for some reason. Apart from this, the code runs fine and does what's expected. The MDA occurs before the control reached the first line of the method.

How can I do it properly? What is the root cause of the problem?

Tamás Szelei
  • 23,169
  • 18
  • 105
  • 180
  • You should remove the braces around the nested `using` statements; it will make your code much shorter. http://stackoverflow.com/questions/1329739/nested-using-statements-in-c/1329765#1329765 – SLaks May 08 '11 at 13:18
  • Added stacktrace and reformatted code. – Tamás Szelei May 08 '11 at 13:28
  • This is coming from the finalizer thread. It's coming from some piece of code that executed before the time that the exception was thrown. – SLaks May 08 '11 at 13:30
  • I figured that in the meantime. I also found this: http://msdn.microsoft.com/en-us/library/85eak4a0(VS.90).aspx So it's not really an exception but an MDA. The troubleshooting tips did not help at all. I do not access and Handles let alone copy it. – Tamás Szelei May 08 '11 at 13:33

1 Answers1

7

Clearly the finalizer thread is finalizing a SafeHandle that is already disposed. This is the implementation of the AesCryptoServiceProvider.Dispose(bool) method:

protected override void Dispose(bool disposing)
{
    try {
        if (disposing) {
            if (this.m_key != null) this.m_key.Dispose();
            if (this.m_cspHandle != null) this.m_cspHandle.Dispose();
        }
    }
    finally {
        base.Dispose(disposing);
    }
}

Three bugs:

  • It doesn't set the m_key field to null after disposing it
  • GC.SuppressFinalize() isn't called, not even by the base class in .NET 3.5
  • The SafeCapiKeyHandle class doesn't invalidate the stored handle in its ReleaseHandle() method.

The combination of all three bugs is enough to trigger this MDA. It is still bugged in .NET 4.0 but at least GC.SuppressFinalize is being called by SymmetricAlgorithm.Dispose(bool) so that the finalizer won't be used.

Inspiring to see the framework masters screw up. You can report the problem at connect.microsoft.com. To stop the debugger from nagging you about this use Debug + Exceptions, Managed Debugging Assistants, untick ReleaseHandleFailed. This one is unticked by default, surely the reason you are the first to notice this bug.

I think the third bug makes this a critical problem btw, it is technically possible for this bug to cause a recycled handle value to be closed. Very small odds though. Rather ironic, given that this is a safe handle class.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Masterful observation. Yes, I ticked some checkboxes, because for some reason, all exceptions were turned off, and I didn't know the defaults. – Tamás Szelei May 08 '11 at 17:44