From db8a764da1f1b63403bab27e1224a9a41b3b6df1 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Thu, 11 Apr 2024 11:23:04 -0400 Subject: [PATCH] Prevent concurrent use of cryptographic hash algorithms --- .../src/Resources/Strings.resx | 3 + .../src/System.Security.Cryptography.csproj | 2 + .../Security/Cryptography/ConcurrencyBlock.cs | 40 +++ .../Cryptography/ConcurrentSafeKmac.cs | 52 ++++ .../Security/Cryptography/HashProviderCng.cs | 66 +++-- .../HashProviderDispenser.Apple.cs | 73 ++++-- .../HashProviderDispenser.Unix.cs | 66 +++-- .../System/Security/Cryptography/Kmac128.cs | 4 +- .../System/Security/Cryptography/Kmac256.cs | 4 +- .../Security/Cryptography/KmacXof128.cs | 4 +- .../Security/Cryptography/KmacXof256.cs | 4 +- .../System/Security/Cryptography/Shake128.cs | 39 ++- .../System/Security/Cryptography/Shake256.cs | 39 ++- .../tests/HashAlgorithmTestDriver.cs | 228 +++++++++++++++++- .../tests/IncrementalHashTests.cs | 102 ++++++++ .../tests/MD5Tests.cs | 2 +- .../tests/Sha1ManagedTests.cs | 8 +- .../tests/Sha1Tests.cs | 7 +- .../tests/Sha256ManagedTests.cs | 8 +- .../tests/Sha256Tests.cs | 7 +- .../tests/Sha384ManagedTests.cs | 8 +- .../tests/Sha384Tests.cs | 7 +- .../tests/Sha3_256Tests.cs | 2 +- .../tests/Sha3_384Tests.cs | 2 +- .../tests/Sha3_512Tests.cs | 2 +- .../tests/Sha512ManagedTests.cs | 8 +- .../tests/Sha512Tests.cs | 7 +- .../tests/ShakeTestDriver.cs | 44 ++++ 28 files changed, 714 insertions(+), 124 deletions(-) create mode 100644 src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ConcurrencyBlock.cs create mode 100644 src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ConcurrentSafeKmac.cs diff --git a/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx index a0a9e1e4afcf7..b121f8fd75c2d 100644 --- a/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx @@ -273,6 +273,9 @@ The specified CipherMode '{0}' is not supported. + + Concurrent operations from multiple threads on this type are not supported. + This key is for algorithm '{0}'. Expected '{1}'. diff --git a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj index 7e24c20a7fc41..ae5affcbb971a 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -371,6 +371,8 @@ + + diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ConcurrencyBlock.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ConcurrencyBlock.cs new file mode 100644 index 0000000000000..c0eafe849da90 --- /dev/null +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ConcurrencyBlock.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading; + +namespace System.Security.Cryptography +{ + internal struct ConcurrencyBlock + { + private int _count; + + internal static Scope Enter(ref ConcurrencyBlock block) + { + int count = Interlocked.Increment(ref block._count); + + if (count != 1) + { + Interlocked.Decrement(ref block._count); + throw new CryptographicException(SR.Cryptography_ConcurrentUseNotSupported); + } + + return new Scope(ref block._count); + } + + internal ref struct Scope + { + private ref int _parentCount; + + internal Scope(ref int parentCount) + { + _parentCount = ref parentCount; + } + + internal void Dispose() + { + Interlocked.Decrement(ref _parentCount); + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ConcurrentSafeKmac.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ConcurrentSafeKmac.cs new file mode 100644 index 0000000000000..ca41668473fa6 --- /dev/null +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ConcurrentSafeKmac.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Security.Cryptography +{ + internal struct ConcurrentSafeKmac + { + private readonly LiteKmac _liteKmac; + private ConcurrencyBlock _block; + + public int HashSizeInBytes => _liteKmac.HashSizeInBytes; + + internal ConcurrentSafeKmac(string algorithmId, ReadOnlySpan key, ReadOnlySpan customizationString, bool xof) + { + _liteKmac = LiteHashProvider.CreateKmac(algorithmId, key, customizationString, xof); + } + + public void Append(ReadOnlySpan data) + { + using (ConcurrencyBlock.Enter(ref _block)) + { + _liteKmac.Append(data); + } + } + + public int Current(Span destination) + { + using (ConcurrencyBlock.Enter(ref _block)) + { + return _liteKmac.Current(destination); + } + } + + public int Finalize(Span destination) + { + using (ConcurrencyBlock.Enter(ref _block)) + { + return _liteKmac.Finalize(destination); + } + } + + public void Reset() + { + using (ConcurrencyBlock.Enter(ref _block)) + { + _liteKmac.Reset(); + } + } + + public void Dispose() => _liteKmac.Dispose(); + } +} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderCng.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderCng.cs index 36f4767e9939e..7e83210727f04 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderCng.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderCng.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Threading; using Microsoft.Win32.SafeHandles; using BCryptCreateHashFlags = Interop.BCrypt.BCryptCreateHashFlags; using BCryptOpenAlgorithmProviderFlags = Interop.BCrypt.BCryptOpenAlgorithmProviderFlags; @@ -63,47 +64,57 @@ internal HashProviderCng(string hashAlgId, ReadOnlySpan key, bool isHmac) public sealed override unsafe void AppendHashData(ReadOnlySpan source) { Debug.Assert(_hHash != null); - NTSTATUS ntStatus = Interop.BCrypt.BCryptHashData(_hHash, source, source.Length, 0); - if (ntStatus != NTSTATUS.STATUS_SUCCESS) + + using (ConcurrencyBlock.Enter(ref _block)) { - throw Interop.BCrypt.CreateCryptographicException(ntStatus); - } + NTSTATUS ntStatus = Interop.BCrypt.BCryptHashData(_hHash, source, source.Length, 0); + if (ntStatus != NTSTATUS.STATUS_SUCCESS) + { + throw Interop.BCrypt.CreateCryptographicException(ntStatus); + } - _running = true; + _running = true; + } } public override int FinalizeHashAndReset(Span destination) { Debug.Assert(destination.Length >= _hashSize); - Debug.Assert(_hHash != null); - NTSTATUS ntStatus = Interop.BCrypt.BCryptFinishHash(_hHash, destination, _hashSize, 0); - if (ntStatus != NTSTATUS.STATUS_SUCCESS) + + using (ConcurrencyBlock.Enter(ref _block)) { - throw Interop.BCrypt.CreateCryptographicException(ntStatus); - } + NTSTATUS ntStatus = Interop.BCrypt.BCryptFinishHash(_hHash, destination, _hashSize, 0); + + if (ntStatus != NTSTATUS.STATUS_SUCCESS) + { + throw Interop.BCrypt.CreateCryptographicException(ntStatus); + } - _running = false; - Reset(); - return _hashSize; + _running = false; + Reset(); + return _hashSize; + } } public override int GetCurrentHash(Span destination) { Debug.Assert(destination.Length >= _hashSize); - Debug.Assert(_hHash != null); - using (SafeBCryptHashHandle tmpHash = Interop.BCrypt.BCryptDuplicateHash(_hHash)) + using (ConcurrencyBlock.Enter(ref _block)) { - NTSTATUS ntStatus = Interop.BCrypt.BCryptFinishHash(tmpHash, destination, _hashSize, 0); - - if (ntStatus != NTSTATUS.STATUS_SUCCESS) + using (SafeBCryptHashHandle tmpHash = Interop.BCrypt.BCryptDuplicateHash(_hHash)) { - throw Interop.BCrypt.CreateCryptographicException(ntStatus); - } + NTSTATUS ntStatus = Interop.BCrypt.BCryptFinishHash(tmpHash, destination, _hashSize, 0); - return _hashSize; + if (ntStatus != NTSTATUS.STATUS_SUCCESS) + { + throw Interop.BCrypt.CreateCryptographicException(ntStatus); + } + + return _hashSize; + } } } @@ -125,21 +136,27 @@ public sealed override void Dispose(bool disposing) public override void Reset() { + // Reset does not need to use ConcurrencyBlock. It either no-ops, or creates an entirely new handle, exchanges + // them, and disposes of the old handle. We don't need to block concurrency on the Dispose because SafeHandle + // does that. if (_reusable && !_running) return; - DestroyHash(); - BCryptCreateHashFlags flags = _reusable ? BCryptCreateHashFlags.BCRYPT_HASH_REUSABLE_FLAG : BCryptCreateHashFlags.None; SafeBCryptHashHandle hHash; NTSTATUS ntStatus = Interop.BCrypt.BCryptCreateHash(_hAlgorithm, out hHash, IntPtr.Zero, 0, _key, _key == null ? 0 : _key.Length, flags); + if (ntStatus != NTSTATUS.STATUS_SUCCESS) + { + hHash.Dispose(); throw Interop.BCrypt.CreateCryptographicException(ntStatus); + } - _hHash = hHash; + SafeBCryptHashHandle? previousHash = Interlocked.Exchange(ref _hHash, hHash); + previousHash?.Dispose(); } private void DestroyHash() @@ -161,5 +178,6 @@ private void DestroyHash() private readonly int _hashSize; private bool _running; + private ConcurrencyBlock _block; } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Apple.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Apple.cs index 58e63629300a7..fdd414d59ff23 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Apple.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Apple.cs @@ -141,6 +141,7 @@ private sealed class AppleDigestProvider : HashProvider { private readonly LiteHash _liteHash; private bool _running; + private ConcurrencyBlock _block; public AppleDigestProvider(string hashAlgorithmId) { @@ -149,21 +150,30 @@ public AppleDigestProvider(string hashAlgorithmId) public override void AppendHashData(ReadOnlySpan data) { - _liteHash.Append(data); - _running = true; + using (ConcurrencyBlock.Enter(ref _block)) + { + _liteHash.Append(data); + _running = true; + } } public override int FinalizeHashAndReset(Span destination) { - int written = _liteHash.Finalize(destination); - // Apple's DigestFinal self-resets, so don't bother calling reset. - _running = false; - return written; + using (ConcurrencyBlock.Enter(ref _block)) + { + int written = _liteHash.Finalize(destination); + // Apple's DigestFinal self-resets, so don't bother calling reset. + _running = false; + return written; + } } public override int GetCurrentHash(Span destination) { - return _liteHash.Current(destination); + using (ConcurrencyBlock.Enter(ref _block)) + { + return _liteHash.Current(destination); + } } public override int HashSizeInBytes => _liteHash.HashSizeInBytes; @@ -178,10 +188,13 @@ public override void Dispose(bool disposing) public override void Reset() { - if (_running) + using (ConcurrencyBlock.Enter(ref _block)) { - _liteHash.Reset(); - _running = false; + if (_running) + { + _liteHash.Reset(); + _running = false; + } } } } @@ -191,6 +204,7 @@ private sealed class AppleHmacProvider : HashProvider private readonly LiteHmac _liteHmac; private readonly byte[] _key; private bool _running; + private ConcurrencyBlock _block; public AppleHmacProvider(string hashAlgorithmId, ReadOnlySpan key) { @@ -201,36 +215,45 @@ public AppleHmacProvider(string hashAlgorithmId, ReadOnlySpan key) public override void AppendHashData(ReadOnlySpan data) { - if (!_running) + using (ConcurrencyBlock.Enter(ref _block)) { - _liteHmac.Reset(_key); - } + if (!_running) + { + _liteHmac.Reset(_key); + } - _liteHmac.Append(data); - _running = true; + _liteHmac.Append(data); + _running = true; + } } public override int FinalizeHashAndReset(Span destination) { - if (!_running) + using (ConcurrencyBlock.Enter(ref _block)) { + if (!_running) + { + _liteHmac.Reset(_key); + } + + int written = _liteHmac.Finalize(destination); _liteHmac.Reset(_key); + _running = false; + return written; } - - int written = _liteHmac.Finalize(destination); - _liteHmac.Reset(_key); - _running = false; - return written; } public override int GetCurrentHash(Span destination) { - if (!_running) + using (ConcurrencyBlock.Enter(ref _block)) { - _liteHmac.Reset(_key); - } + if (!_running) + { + _liteHmac.Reset(_key); + } - return _liteHmac.Current(destination); + return _liteHmac.Current(destination); + } } public override int HashSizeInBytes => _liteHmac.HashSizeInBytes; diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Unix.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Unix.cs index d62505e88873d..18e8659d0cbfc 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Unix.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Unix.cs @@ -87,6 +87,7 @@ private sealed class EvpHashProvider : HashProvider { private readonly LiteHash _liteHash; private bool _running; + private ConcurrencyBlock _block; public EvpHashProvider(string hashAlgorithmId) { @@ -95,21 +96,30 @@ public EvpHashProvider(string hashAlgorithmId) public override void AppendHashData(ReadOnlySpan data) { - _liteHash.Append(data); - _running = true; + using (ConcurrencyBlock.Enter(ref _block)) + { + _liteHash.Append(data); + _running = true; + } } public override int FinalizeHashAndReset(Span destination) { - int written = _liteHash.Finalize(destination); - _liteHash.Reset(); - _running = false; - return written; + using (ConcurrencyBlock.Enter(ref _block)) + { + int written = _liteHash.Finalize(destination); + _liteHash.Reset(); + _running = false; + return written; + } } public override int GetCurrentHash(Span destination) { - return _liteHash.Current(destination); + using (ConcurrencyBlock.Enter(ref _block)) + { + return _liteHash.Current(destination); + } } public override int HashSizeInBytes => _liteHash.HashSizeInBytes; @@ -124,10 +134,13 @@ public override void Dispose(bool disposing) public override void Reset() { - if (_running) + using (ConcurrencyBlock.Enter(ref _block)) { - _liteHash.Reset(); - _running = false; + if (_running) + { + _liteHash.Reset(); + _running = false; + } } } } @@ -136,6 +149,7 @@ private sealed class HmacHashProvider : HashProvider { private readonly LiteHmac _liteHmac; private bool _running; + private ConcurrencyBlock _block; public HmacHashProvider(string hashAlgorithmId, ReadOnlySpan key) { @@ -144,21 +158,30 @@ public HmacHashProvider(string hashAlgorithmId, ReadOnlySpan key) public override void AppendHashData(ReadOnlySpan data) { - _liteHmac.Append(data); - _running = true; + using (ConcurrencyBlock.Enter(ref _block)) + { + _liteHmac.Append(data); + _running = true; + } } public override int FinalizeHashAndReset(Span destination) { - int written = _liteHmac.Finalize(destination); - _liteHmac.Reset(); - _running = false; - return written; + using (ConcurrencyBlock.Enter(ref _block)) + { + int written = _liteHmac.Finalize(destination); + _liteHmac.Reset(); + _running = false; + return written; + } } public override int GetCurrentHash(Span destination) { - return _liteHmac.Current(destination); + using (ConcurrencyBlock.Enter(ref _block)) + { + return _liteHmac.Current(destination); + } } public override int HashSizeInBytes => _liteHmac.HashSizeInBytes; @@ -173,10 +196,13 @@ public override void Dispose(bool disposing) public override void Reset() { - if (_running) + using (ConcurrencyBlock.Enter(ref _block)) { - _liteHmac.Reset(); - _running = false; + if (_running) + { + _liteHmac.Reset(); + _running = false; + } } } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Kmac128.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Kmac128.cs index c850977ef9b8b..a90c987a8278d 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Kmac128.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Kmac128.cs @@ -19,7 +19,7 @@ namespace System.Security.Cryptography /// public sealed class Kmac128 : IDisposable { - private readonly LiteKmac _kmacProvider; + private ConcurrentSafeKmac _kmacProvider; private bool _disposed; /// @@ -53,7 +53,7 @@ public Kmac128(byte[] key, byte[]? customizationString = null) public Kmac128(ReadOnlySpan key, ReadOnlySpan customizationString = default) { CheckPlatformSupport(); - _kmacProvider = LiteHashProvider.CreateKmac(HashAlgorithmNames.KMAC128, key, customizationString, xof: false); + _kmacProvider = new ConcurrentSafeKmac(HashAlgorithmNames.KMAC128, key, customizationString, xof: false); } /// diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Kmac256.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Kmac256.cs index 3ee2ff818191c..d5c3d013e31e7 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Kmac256.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Kmac256.cs @@ -19,7 +19,7 @@ namespace System.Security.Cryptography /// public sealed class Kmac256 : IDisposable { - private readonly LiteKmac _kmacProvider; + private ConcurrentSafeKmac _kmacProvider; private bool _disposed; /// @@ -53,7 +53,7 @@ public Kmac256(byte[] key, byte[]? customizationString = null) public Kmac256(ReadOnlySpan key, ReadOnlySpan customizationString = default) { CheckPlatformSupport(); - _kmacProvider = LiteHashProvider.CreateKmac(HashAlgorithmNames.KMAC256, key, customizationString, xof: false); + _kmacProvider = new ConcurrentSafeKmac(HashAlgorithmNames.KMAC256, key, customizationString, xof: false); } /// diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/KmacXof128.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/KmacXof128.cs index 3514fbaaa0c9b..7275d822b136c 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/KmacXof128.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/KmacXof128.cs @@ -19,7 +19,7 @@ namespace System.Security.Cryptography /// public sealed class KmacXof128 : IDisposable { - private readonly LiteKmac _kmacProvider; + private ConcurrentSafeKmac _kmacProvider; private bool _disposed; /// @@ -53,7 +53,7 @@ public KmacXof128(byte[] key, byte[]? customizationString = null) public KmacXof128(ReadOnlySpan key, ReadOnlySpan customizationString = default) { CheckPlatformSupport(); - _kmacProvider = LiteHashProvider.CreateKmac(HashAlgorithmNames.KMAC128, key, customizationString, xof: true); + _kmacProvider = new ConcurrentSafeKmac(HashAlgorithmNames.KMAC128, key, customizationString, xof: true); } /// diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/KmacXof256.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/KmacXof256.cs index 040fe7a37ec01..fe08a87d494fd 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/KmacXof256.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/KmacXof256.cs @@ -19,7 +19,7 @@ namespace System.Security.Cryptography /// public sealed class KmacXof256 : IDisposable { - private readonly LiteKmac _kmacProvider; + private ConcurrentSafeKmac _kmacProvider; private bool _disposed; /// @@ -53,7 +53,7 @@ public KmacXof256(byte[] key, byte[]? customizationString = null) public KmacXof256(ReadOnlySpan key, ReadOnlySpan customizationString = default) { CheckPlatformSupport(); - _kmacProvider = LiteHashProvider.CreateKmac(HashAlgorithmNames.KMAC256, key, customizationString, xof: true); + _kmacProvider = new ConcurrentSafeKmac(HashAlgorithmNames.KMAC256, key, customizationString, xof: true); } /// diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Shake128.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Shake128.cs index a8b5e5f3257f2..14dc8e0eac407 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Shake128.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Shake128.cs @@ -22,6 +22,7 @@ public sealed partial class Shake128 : IDisposable // Some platforms have a mutable struct for LiteXof, do not mark this field as readonly. private LiteXof _hashProvider; private bool _disposed; + private ConcurrencyBlock _block; /// /// Initializes a new instance of the class. @@ -68,7 +69,10 @@ public void AppendData(ReadOnlySpan data) { CheckDisposed(); - _hashProvider.Append(data); + using (ConcurrencyBlock.Enter(ref _block)) + { + _hashProvider.Append(data); + } } /// @@ -87,10 +91,13 @@ public byte[] GetHashAndReset(int outputLength) ArgumentOutOfRangeException.ThrowIfNegative(outputLength); CheckDisposed(); - byte[] output = new byte[outputLength]; - _hashProvider.Finalize(output); - _hashProvider.Reset(); - return output; + using (ConcurrencyBlock.Enter(ref _block)) + { + byte[] output = new byte[outputLength]; + _hashProvider.Finalize(output); + _hashProvider.Reset(); + return output; + } } /// @@ -104,8 +111,11 @@ public void GetHashAndReset(Span destination) { CheckDisposed(); - _hashProvider.Finalize(destination); - _hashProvider.Reset(); + using (ConcurrencyBlock.Enter(ref _block)) + { + _hashProvider.Finalize(destination); + _hashProvider.Reset(); + } } /// @@ -124,9 +134,12 @@ public byte[] GetCurrentHash(int outputLength) ArgumentOutOfRangeException.ThrowIfNegative(outputLength); CheckDisposed(); - byte[] output = new byte[outputLength]; - _hashProvider.Current(output); - return output; + using (ConcurrencyBlock.Enter(ref _block)) + { + byte[] output = new byte[outputLength]; + _hashProvider.Current(output); + return output; + } } /// @@ -139,7 +152,11 @@ public byte[] GetCurrentHash(int outputLength) public void GetCurrentHash(Span destination) { CheckDisposed(); - _hashProvider.Current(destination); + + using (ConcurrencyBlock.Enter(ref _block)) + { + _hashProvider.Current(destination); + } } /// diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Shake256.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Shake256.cs index cafe8d3036334..c801adde9395c 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Shake256.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Shake256.cs @@ -22,6 +22,7 @@ public sealed partial class Shake256 : IDisposable // Some platforms have a mutable struct for LiteXof, do not mark this field as readonly. private LiteXof _hashProvider; private bool _disposed; + private ConcurrencyBlock _block; /// /// Initializes a new instance of the class. @@ -68,7 +69,10 @@ public void AppendData(ReadOnlySpan data) { CheckDisposed(); - _hashProvider.Append(data); + using (ConcurrencyBlock.Enter(ref _block)) + { + _hashProvider.Append(data); + } } /// @@ -87,10 +91,13 @@ public byte[] GetHashAndReset(int outputLength) ArgumentOutOfRangeException.ThrowIfNegative(outputLength); CheckDisposed(); - byte[] output = new byte[outputLength]; - _hashProvider.Finalize(output); - _hashProvider.Reset(); - return output; + using (ConcurrencyBlock.Enter(ref _block)) + { + byte[] output = new byte[outputLength]; + _hashProvider.Finalize(output); + _hashProvider.Reset(); + return output; + } } /// @@ -104,8 +111,11 @@ public void GetHashAndReset(Span destination) { CheckDisposed(); - _hashProvider.Finalize(destination); - _hashProvider.Reset(); + using (ConcurrencyBlock.Enter(ref _block)) + { + _hashProvider.Finalize(destination); + _hashProvider.Reset(); + } } /// @@ -124,9 +134,12 @@ public byte[] GetCurrentHash(int outputLength) ArgumentOutOfRangeException.ThrowIfNegative(outputLength); CheckDisposed(); - byte[] output = new byte[outputLength]; - _hashProvider.Current(output); - return output; + using (ConcurrencyBlock.Enter(ref _block)) + { + byte[] output = new byte[outputLength]; + _hashProvider.Current(output); + return output; + } } /// @@ -139,7 +152,11 @@ public byte[] GetCurrentHash(int outputLength) public void GetCurrentHash(Span destination) { CheckDisposed(); - _hashProvider.Current(destination); + + using (ConcurrencyBlock.Enter(ref _block)) + { + _hashProvider.Current(destination); + } } /// diff --git a/src/libraries/System.Security.Cryptography/tests/HashAlgorithmTestDriver.cs b/src/libraries/System.Security.Cryptography/tests/HashAlgorithmTestDriver.cs index 2fa0779a257e4..bbcf23ec0e596 100644 --- a/src/libraries/System.Security.Cryptography/tests/HashAlgorithmTestDriver.cs +++ b/src/libraries/System.Security.Cryptography/tests/HashAlgorithmTestDriver.cs @@ -5,6 +5,8 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.DotNet.RemoteExecutor; +using Microsoft.DotNet.XUnitExtensions; using Test.Cryptography; using Xunit; @@ -14,7 +16,7 @@ public abstract class HashAlgorithmTestDriver where THashTrait : IHa { public static bool IsSupported => THashTrait.IsSupported; public static bool IsNotSupported => !IsSupported; - protected abstract HashAlgorithm Create(); + protected HashAlgorithm Create() => THashTrait.Create(); protected abstract bool TryHashData(ReadOnlySpan source, Span destination, out int bytesWritten); protected abstract byte[] HashData(byte[] source); protected abstract byte[] HashData(ReadOnlySpan source); @@ -930,11 +932,235 @@ public void CryptographicOperations_HashData_ArgValidation_UnreadableStream() Assert.Throws("source", () => CryptographicOperations.HashDataAsync(HashAlgorithm, UntouchableStream.Instance, Memory.Empty)); } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void HashAlgorithm_ComputeHash_ConcurrentUseDoesNotCrashProcess() + { + if (!IsSupported) + { + throw new SkipTestException("Algorithm is not supported on this platform."); + } + + static void Update(object obj) + { + byte[] data = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + + for (int i = 0; i < 10_000; i++) + { + try + { + ((HashAlgorithm)obj).ComputeHash(data); + } + catch + { + // Ignore all managed exceptions. HashAlgorithm is not thread safe, but we don't want process + // crashes. + } + } + } + + RemoteExecutor.Invoke(static () => + { + using (HashAlgorithm hash = THashTrait.Create()) + { + Thread thread1 = new(Update); + Thread thread2 = new(Update); + thread1.Start(hash); + thread2.Start(hash); + thread1.Join(); + thread2.Join(); + } + + return RemoteExecutor.SuccessExitCode; + }).Dispose(); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void HashAlgorithm_TransformBlock_ConcurrentUseDoesNotCrashProcess() + { + if (!IsSupported) + { + throw new SkipTestException("Algorithm is not supported on this platform."); + } + + static void Update(object obj) + { + byte[] data = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + + for (int i = 0; i < 10_000; i++) + { + try + { + ((HashAlgorithm)obj).TransformBlock(data, 0, data.Length, null, 0); + } + catch + { + // Ignore all managed exceptions. HashAlgorithm is not thread safe, but we don't want process + // crashes. + } + } + } + + RemoteExecutor.Invoke(static () => + { + using (HashAlgorithm hash = THashTrait.Create()) + { + Thread thread1 = new(Update); + Thread thread2 = new(Update); + thread1.Start(hash); + thread2.Start(hash); + thread1.Join(); + thread2.Join(); + } + + return RemoteExecutor.SuccessExitCode; + }).Dispose(); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void HashAlgorithm_TransformFinalBlock_ConcurrentUseDoesNotCrashProcess() + { + if (!IsSupported) + { + throw new SkipTestException("Algorithm is not supported on this platform."); + } + + static void Update(object obj) + { + byte[] data = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + + for (int i = 0; i < 10_000; i++) + { + try + { + ((HashAlgorithm)obj).TransformFinalBlock(data, 0, data.Length); + } + catch + { + // Ignore all managed exceptions. HashAlgorithm is not thread safe, but we don't want process + // crashes. + } + } + } + + RemoteExecutor.Invoke(static () => + { + using (HashAlgorithm hash = THashTrait.Create()) + { + Thread thread1 = new(Update); + Thread thread2 = new(Update); + thread1.Start(hash); + thread2.Start(hash); + thread1.Join(); + thread2.Join(); + } + + return RemoteExecutor.SuccessExitCode; + }).Dispose(); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void HashAlgorithm_TransformBlockAndInitialize_ConcurrentUseDoesNotCrashProcess() + { + if (!IsSupported) + { + throw new SkipTestException("Algorithm is not supported on this platform."); + } + + static void Update(object obj) + { + byte[] data = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + + for (int i = 0; i < 10_000; i++) + { + try + { + HashAlgorithm hash = ((HashAlgorithm)obj); + hash.TransformBlock(data, 0, data.Length, null, 0); + hash.Initialize(); + } + catch + { + // Ignore all managed exceptions. HashAlgorithm is not thread safe, but we don't want process + // crashes. + } + } + } + + RemoteExecutor.Invoke(static () => + { + using (HashAlgorithm hash = THashTrait.Create()) + { + Thread thread1 = new(Update); + Thread thread2 = new(Update); + thread1.Start(hash); + thread2.Start(hash); + thread1.Join(); + thread2.Join(); + } + + return RemoteExecutor.SuccessExitCode; + }).Dispose(); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void HashAlgorithm_TransformBlockAndDispose_ConcurrentUseDoesNotCrashProcess() + { + if (!IsSupported) + { + throw new SkipTestException("Algorithm is not supported on this platform."); + } + + static void Update(object obj) + { + byte[] data = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + + for (int i = 0; i < 10_000; i++) + { + try + { + HashAlgorithm hash = ((HashAlgorithm)obj); + hash.TransformBlock(data, 0, data.Length, null, 0); + } + catch + { + // Ignore all managed exceptions. HashAlgorithm is not thread safe, but we don't want process + // crashes. + } + } + } + + RemoteExecutor.Invoke(static () => + { + using (HashAlgorithm hash = THashTrait.Create()) + { + Thread thread1 = new(Update); + Thread thread2 = new(obj => + { + Thread.Sleep(10); + try + { + ((HashAlgorithm)obj).Dispose(); + } + catch + { + } + }); + thread1.Start(hash); + thread2.Start(hash); + thread1.Join(); + thread2.Join(); + } + + return RemoteExecutor.SuccessExitCode; + }).Dispose(); + } } public interface IHashTrait { static abstract bool IsSupported { get; } static abstract int HashSizeInBytes { get; } + static abstract HashAlgorithm Create(); } } diff --git a/src/libraries/System.Security.Cryptography/tests/IncrementalHashTests.cs b/src/libraries/System.Security.Cryptography/tests/IncrementalHashTests.cs index dc7c33debfa8c..69788b2402323 100644 --- a/src/libraries/System.Security.Cryptography/tests/IncrementalHashTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/IncrementalHashTests.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Threading; +using Microsoft.DotNet.RemoteExecutor; using Test.Cryptography; using Xunit; @@ -622,6 +624,106 @@ public static void VerifyBounds_GetHashAndReset_Hash(HashAlgorithm referenceAlgo } } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public static void Hash_GetHashAndReset_ConcurrentUseDoesNotCrashProcess() + { + static void ThreadWork(object obj) + { + try + { + IncrementalHash hash = (IncrementalHash)obj; + + for (int i = 0; i < 10_000; i++) + { + hash.AppendData("potatos and carrots make for a fine stew."u8); + hash.GetHashAndReset(); + } + } + catch + { + // Ignore all managed exceptions. IncrementalHash is not thread safe, but we don't want process + // crashes. + } + } + + RemoteExecutor.Invoke(static () => + { + foreach(object[] items in GetHashAlgorithms()) + { + if (items is [HashAlgorithm referenceAlgorithm, HashAlgorithmName hashAlgorithm]) + { + referenceAlgorithm.Dispose(); + + using (IncrementalHash hash = IncrementalHash.CreateHash(hashAlgorithm)) + { + Thread thread1 = new(ThreadWork); + Thread thread2 = new(ThreadWork); + thread1.Start(hash); + thread2.Start(hash); + thread1.Join(); + thread2.Join(); + } + } + else + { + Assert.Fail("Test is not set up correctly."); + } + } + + return RemoteExecutor.SuccessExitCode; + }).Dispose(); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public static void HMAC_GetHashAndReset_ConcurrentUseDoesNotCrashProcess() + { + static void ThreadWork(object obj) + { + try + { + IncrementalHash hash = (IncrementalHash)obj; + + for (int i = 0; i < 10_000; i++) + { + hash.AppendData("potatos and carrots make for a fine stew."u8); + hash.GetHashAndReset(); + } + } + catch + { + // Ignore all managed exceptions. IncrementalHash is not thread safe, but we don't want process + // crashes. + } + } + + RemoteExecutor.Invoke(static () => + { + foreach(object[] items in GetHMACs()) + { + if (items is [HashAlgorithm referenceAlgorithm, HashAlgorithmName hashAlgorithm]) + { + referenceAlgorithm.Dispose(); + + using (IncrementalHash hash = IncrementalHash.CreateHMAC(hashAlgorithm, [1, 2, 3, 4])) + { + Thread thread1 = new(ThreadWork); + Thread thread2 = new(ThreadWork); + thread1.Start(hash); + thread2.Start(hash); + thread1.Join(); + thread2.Join(); + } + } + else + { + Assert.Fail("Test is not set up correctly."); + } + } + + return RemoteExecutor.SuccessExitCode; + }).Dispose(); + } + private static void VerifyGetCurrentHash(IncrementalHash single, IncrementalHash accumulated) { Span buf = stackalloc byte[2048]; diff --git a/src/libraries/System.Security.Cryptography/tests/MD5Tests.cs b/src/libraries/System.Security.Cryptography/tests/MD5Tests.cs index 8fe91caf0d1b7..6e9f4a724fffc 100644 --- a/src/libraries/System.Security.Cryptography/tests/MD5Tests.cs +++ b/src/libraries/System.Security.Cryptography/tests/MD5Tests.cs @@ -15,9 +15,9 @@ public sealed class Traits : IHashTrait { public static bool IsSupported => true; public static int HashSizeInBytes => MD5.HashSizeInBytes; + public static HashAlgorithm Create() => MD5.Create(); } - protected override HashAlgorithm Create() => MD5.Create(); protected override HashAlgorithmName HashAlgorithm => HashAlgorithmName.MD5; protected override bool TryHashData(ReadOnlySpan source, Span destination, out int bytesWritten) diff --git a/src/libraries/System.Security.Cryptography/tests/Sha1ManagedTests.cs b/src/libraries/System.Security.Cryptography/tests/Sha1ManagedTests.cs index 54e7476f54471..7d9fee4ee137c 100644 --- a/src/libraries/System.Security.Cryptography/tests/Sha1ManagedTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/Sha1ManagedTests.cs @@ -6,11 +6,13 @@ namespace System.Security.Cryptography.Tests /// /// Sha1Managed has a copy of the same implementation as SHA1 /// - public class Sha1ManagedTests : Sha1Tests + public class Sha1ManagedTests : Sha1Tests { - protected override HashAlgorithm Create() + public sealed class Traits : IHashTrait { - return new SHA1Managed(); + public static bool IsSupported => true; + public static int HashSizeInBytes => SHA1.HashSizeInBytes; + public static HashAlgorithm Create() => new SHA1Managed(); } } } diff --git a/src/libraries/System.Security.Cryptography/tests/Sha1Tests.cs b/src/libraries/System.Security.Cryptography/tests/Sha1Tests.cs index 691dd9ea45e14..ede541452f59f 100644 --- a/src/libraries/System.Security.Cryptography/tests/Sha1Tests.cs +++ b/src/libraries/System.Security.Cryptography/tests/Sha1Tests.cs @@ -8,15 +8,18 @@ namespace System.Security.Cryptography.Tests { - public class Sha1Tests : HashAlgorithmTestDriver + public sealed class FactorySha1Tests : Sha1Tests { public sealed class Traits : IHashTrait { public static bool IsSupported => true; public static int HashSizeInBytes => SHA1.HashSizeInBytes; + public static HashAlgorithm Create() => SHA1.Create(); } + } - protected override HashAlgorithm Create() => SHA1.Create(); + public abstract class Sha1Tests : HashAlgorithmTestDriver where THashTrait : IHashTrait + { protected override HashAlgorithmName HashAlgorithm => HashAlgorithmName.SHA1; protected override bool TryHashData(ReadOnlySpan source, Span destination, out int bytesWritten) diff --git a/src/libraries/System.Security.Cryptography/tests/Sha256ManagedTests.cs b/src/libraries/System.Security.Cryptography/tests/Sha256ManagedTests.cs index 2f153da87a60f..3241fe97d5bd9 100644 --- a/src/libraries/System.Security.Cryptography/tests/Sha256ManagedTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/Sha256ManagedTests.cs @@ -6,11 +6,13 @@ namespace System.Security.Cryptography.Tests /// /// Sha256Managed has a copy of the same implementation as SHA256 /// - public class Sha256ManagedTests : Sha256Tests + public class Sha256ManagedTests : Sha256Tests { - protected override HashAlgorithm Create() + public sealed class Traits : IHashTrait { - return new SHA256Managed(); + public static bool IsSupported => true; + public static int HashSizeInBytes => SHA256.HashSizeInBytes; + public static HashAlgorithm Create() => new SHA256Managed(); } } } diff --git a/src/libraries/System.Security.Cryptography/tests/Sha256Tests.cs b/src/libraries/System.Security.Cryptography/tests/Sha256Tests.cs index b10cc017a313e..c19008e5580cc 100644 --- a/src/libraries/System.Security.Cryptography/tests/Sha256Tests.cs +++ b/src/libraries/System.Security.Cryptography/tests/Sha256Tests.cs @@ -8,15 +8,18 @@ namespace System.Security.Cryptography.Tests { - public class Sha256Tests : HashAlgorithmTestDriver + public sealed class FactorySha256Tests : Sha256Tests { public sealed class Traits : IHashTrait { public static bool IsSupported => true; public static int HashSizeInBytes => SHA256.HashSizeInBytes; + public static HashAlgorithm Create() => SHA256.Create(); } + } - protected override HashAlgorithm Create() => SHA256.Create(); + public abstract class Sha256Tests : HashAlgorithmTestDriver where THashTrait : IHashTrait + { protected override HashAlgorithmName HashAlgorithm => HashAlgorithmName.SHA256; protected override bool TryHashData(ReadOnlySpan source, Span destination, out int bytesWritten) diff --git a/src/libraries/System.Security.Cryptography/tests/Sha384ManagedTests.cs b/src/libraries/System.Security.Cryptography/tests/Sha384ManagedTests.cs index 88166dbf0496a..4cf7baa1fa398 100644 --- a/src/libraries/System.Security.Cryptography/tests/Sha384ManagedTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/Sha384ManagedTests.cs @@ -6,11 +6,13 @@ namespace System.Security.Cryptography.Tests /// /// Sha384Managed has a copy of the same implementation as SHA384 /// - public class Sha384ManagedTests : Sha384Tests + public class Sha384ManagedTests : Sha384Tests { - protected override HashAlgorithm Create() + public sealed class Traits : IHashTrait { - return new SHA384Managed(); + public static bool IsSupported => true; + public static int HashSizeInBytes => SHA384.HashSizeInBytes; + public static HashAlgorithm Create() => new SHA384Managed(); } } } diff --git a/src/libraries/System.Security.Cryptography/tests/Sha384Tests.cs b/src/libraries/System.Security.Cryptography/tests/Sha384Tests.cs index ae1a720c51eed..53b557b421904 100644 --- a/src/libraries/System.Security.Cryptography/tests/Sha384Tests.cs +++ b/src/libraries/System.Security.Cryptography/tests/Sha384Tests.cs @@ -8,15 +8,18 @@ namespace System.Security.Cryptography.Tests { - public class Sha384Tests : HashAlgorithmTestDriver + public sealed class FactorySha384Tests : Sha384Tests { public sealed class Traits : IHashTrait { public static bool IsSupported => true; public static int HashSizeInBytes => SHA384.HashSizeInBytes; + public static HashAlgorithm Create() => SHA384.Create(); } + } - protected override HashAlgorithm Create() => SHA384.Create(); + public abstract class Sha384Tests : HashAlgorithmTestDriver where THashTrait : IHashTrait + { protected override HashAlgorithmName HashAlgorithm => HashAlgorithmName.SHA384; protected override bool TryHashData(ReadOnlySpan source, Span destination, out int bytesWritten) diff --git a/src/libraries/System.Security.Cryptography/tests/Sha3_256Tests.cs b/src/libraries/System.Security.Cryptography/tests/Sha3_256Tests.cs index 96fe91e4f3026..f5c002d64e4a9 100644 --- a/src/libraries/System.Security.Cryptography/tests/Sha3_256Tests.cs +++ b/src/libraries/System.Security.Cryptography/tests/Sha3_256Tests.cs @@ -15,9 +15,9 @@ public sealed class Traits : IHashTrait { public static bool IsSupported => SHA3_256.IsSupported; public static int HashSizeInBytes => SHA3_256.HashSizeInBytes; + public static HashAlgorithm Create() => SHA3_256.Create(); } - protected override HashAlgorithm Create() => SHA3_256.Create(); protected override HashAlgorithmName HashAlgorithm => HashAlgorithmName.SHA3_256; protected override bool TryHashData(ReadOnlySpan source, Span destination, out int bytesWritten) diff --git a/src/libraries/System.Security.Cryptography/tests/Sha3_384Tests.cs b/src/libraries/System.Security.Cryptography/tests/Sha3_384Tests.cs index 9e67fb32b7ab2..416e6ae1b6cb8 100644 --- a/src/libraries/System.Security.Cryptography/tests/Sha3_384Tests.cs +++ b/src/libraries/System.Security.Cryptography/tests/Sha3_384Tests.cs @@ -15,9 +15,9 @@ public sealed class Traits : IHashTrait { public static bool IsSupported => SHA3_384.IsSupported; public static int HashSizeInBytes => SHA3_384.HashSizeInBytes; + public static HashAlgorithm Create() => SHA3_384.Create(); } - protected override HashAlgorithm Create() => SHA3_384.Create(); protected override HashAlgorithmName HashAlgorithm => HashAlgorithmName.SHA3_384; protected override bool TryHashData(ReadOnlySpan source, Span destination, out int bytesWritten) diff --git a/src/libraries/System.Security.Cryptography/tests/Sha3_512Tests.cs b/src/libraries/System.Security.Cryptography/tests/Sha3_512Tests.cs index a715cd40c5596..843d87110fc63 100644 --- a/src/libraries/System.Security.Cryptography/tests/Sha3_512Tests.cs +++ b/src/libraries/System.Security.Cryptography/tests/Sha3_512Tests.cs @@ -15,9 +15,9 @@ public sealed class Traits : IHashTrait { public static bool IsSupported => SHA3_512.IsSupported; public static int HashSizeInBytes => SHA3_512.HashSizeInBytes; + public static HashAlgorithm Create() => SHA3_512.Create(); } - protected override HashAlgorithm Create() => SHA3_512.Create(); protected override HashAlgorithmName HashAlgorithm => HashAlgorithmName.SHA3_512; protected override bool TryHashData(ReadOnlySpan source, Span destination, out int bytesWritten) diff --git a/src/libraries/System.Security.Cryptography/tests/Sha512ManagedTests.cs b/src/libraries/System.Security.Cryptography/tests/Sha512ManagedTests.cs index e616e4fea13e0..d6299ec4c84f5 100644 --- a/src/libraries/System.Security.Cryptography/tests/Sha512ManagedTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/Sha512ManagedTests.cs @@ -6,11 +6,13 @@ namespace System.Security.Cryptography.Tests /// /// Sha512Managed has a copy of the same implementation as SHA512 /// - public class Sha512ManagedTests : Sha512Tests + public class Sha512ManagedTests : Sha512Tests { - protected override HashAlgorithm Create() + public sealed class Traits : IHashTrait { - return new SHA512Managed(); + public static bool IsSupported => true; + public static int HashSizeInBytes => SHA512.HashSizeInBytes; + public static HashAlgorithm Create() => new SHA512Managed(); } } } diff --git a/src/libraries/System.Security.Cryptography/tests/Sha512Tests.cs b/src/libraries/System.Security.Cryptography/tests/Sha512Tests.cs index dde06d3646965..7abfdb8a54608 100644 --- a/src/libraries/System.Security.Cryptography/tests/Sha512Tests.cs +++ b/src/libraries/System.Security.Cryptography/tests/Sha512Tests.cs @@ -8,15 +8,18 @@ namespace System.Security.Cryptography.Tests { - public class Sha512Tests : HashAlgorithmTestDriver + public sealed class FactorySha512Tests : Sha512Tests { public sealed class Traits : IHashTrait { public static bool IsSupported => true; public static int HashSizeInBytes => SHA512.HashSizeInBytes; + public static HashAlgorithm Create() => SHA512.Create(); } + } - protected override HashAlgorithm Create() => SHA512.Create(); + public abstract class Sha512Tests : HashAlgorithmTestDriver where THashTrait : IHashTrait + { protected override HashAlgorithmName HashAlgorithm => HashAlgorithmName.SHA512; protected override bool TryHashData(ReadOnlySpan source, Span destination, out int bytesWritten) diff --git a/src/libraries/System.Security.Cryptography/tests/ShakeTestDriver.cs b/src/libraries/System.Security.Cryptography/tests/ShakeTestDriver.cs index f7d1ee2de0701..d0c25b1722eea 100644 --- a/src/libraries/System.Security.Cryptography/tests/ShakeTestDriver.cs +++ b/src/libraries/System.Security.Cryptography/tests/ShakeTestDriver.cs @@ -6,6 +6,8 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.DotNet.RemoteExecutor; +using Microsoft.DotNet.XUnitExtensions; using Xunit; namespace System.Security.Cryptography.Tests @@ -536,5 +538,47 @@ public void IsSupported_AgreesWithPlatform() { Assert.Equal(TShakeTrait.IsSupported, PlatformDetection.SupportsSha3); } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void GetHashAndReset_ConcurrentUseDoesNotCrashProcess() + { + if (!IsSupported) + { + throw new SkipTestException("Algorithm is not supported on this platform."); + } + + RemoteExecutor.Invoke(static () => + { + using (TShake shake = TShakeTrait.Create()) + { + Thread thread1 = new(ThreadWork); + Thread thread2 = new(ThreadWork); + thread1.Start(shake); + thread2.Start(shake); + thread1.Join(); + thread2.Join(); + } + }).Dispose(); + + static void ThreadWork(object obj) + { + TShake shake = (TShake)obj; + + try + { + byte[] input = new byte[128]; + + for (int i = 0; i < 10_000; i++) + { + TShakeTrait.AppendData(shake, input); + TShakeTrait.GetHashAndReset(shake, 128); + } + } + catch + { + // Ignore all managed exceptions. HashAlgorithm is not thread safe, but we don't want process crashes. + } + } + } } }