Getting "ReleaseHandleFailed" MDA in the finalizer thread after using cryptography
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 rea开发者_如何学Pythonson. 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?
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.
精彩评论