Obtenir MDA «ReleaseHandleFailed» dans le thread du finaliseur après avoir utilisé la cryptographie

Je reçois un MDA après avoir exécuté ce code pour la deuxième fois en boucle (avec un paramètre de file différent:

 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)); } } 

Le MDA est le suivant:

Un SafeHandle ou un CriticalHandle de type ‘Microsoft.Win32.SafeHandles.SafeCapiKeyHandle’ n’a pas réussi à libérer correctement le descripteur avec la valeur 0x000000001BEA9B50. Cela indique généralement que la poignée a été mal relâchée via un autre moyen (par exemple, l’extraction de la poignée à l’aide de DangerousGetHandle et sa fermeture directe ou la construction d’un autre SafeHandle autour de celle-ci.)

Le stacktrace n’est pas trop informatif:

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

Je soupçonne qu’un des stream ou CryptoServiceProvider n’est pas publié pour une raison quelconque. En dehors de cela, le code fonctionne bien et fait ce qui est attendu. La MDA se produit avant que le contrôle atteigne la première ligne de la méthode.

Comment puis-je le faire correctement? Quelle est la cause du problème?

Il est clair que le processus de finalisation finalise un SafeHandle déjà supprimé. Voici l’implémentation de la méthode AesCryptoServiceProvider.Dispose (bool):

 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); } } 

Trois bugs:

  • Il ne définit pas le champ m_key sur null après l’avoir supprimé
  • GC.SuppressFinalize () n’est pas appelé, pas même par la classe de base dans .NET 3.5
  • La classe SafeCapiKeyHandle n’invalide pas le handle stocké dans sa méthode ReleaseHandle ().

La combinaison des trois bogues est suffisante pour déclencher ce MDA. Il est toujours buggé dans .NET 4.0 mais au moins GC.SuppressFinalize est appelé par SymmesortingcAlgorithm.Dispose (bool) afin que le finaliseur ne soit pas utilisé.

Inspirant de voir les maîtres du framework bousiller. Vous pouvez signaler le problème à connect.microsoft.com. Pour empêcher le débogueur de vous harceler à propos de cette utilisation, utilisez Debug + Exceptions, Assistants de débogage gérés, décochez ReleaseHandleFailed. Celui-ci est décoché par défaut, ce qui est sûrement la raison pour laquelle vous êtes le premier à remarquer ce bogue.

Je pense que le troisième bogue en fait un problème critique, il est techniquement possible que ce bogue provoque la fermeture d’une valeur de descripteur recyclé. Très petite chance cependant. Plutôt ironique, étant donné qu’il s’agit d’une classe de manipulation sûre.