Skip to content

Commit 595ad02

Browse files
committed
Fix hard crashes for HMAC on Linux
1 parent 741fe4b commit 595ad02

File tree

2 files changed

+173
-11
lines changed

2 files changed

+173
-11
lines changed

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Unix.cs

+71-11
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ public override int GetCurrentHash(Span<byte> destination)
159159

160160
public override void Dispose(bool disposing)
161161
{
162-
// We don't need to worry about the callCount here because the SafeHandle takes care of reference
162+
// We don't need to worry about the _callOperations here because the SafeHandle takes care of reference
163163
// counting the dispose.
164164

165165
if (disposing)
@@ -198,34 +198,79 @@ private sealed class HmacHashProvider : HashProvider
198198
private readonly LiteHmac _liteHmac;
199199
private bool _running;
200200

201+
// HashAlgorithm and IncrementalHash are not thread safe. However, OpenSSL is not thread safe to the point
202+
// that calling EVP_DigestUpdate on an EVP_MD_CTX concurrently may result in process crashes from illegal
203+
// memory accesses. We don't want the process to crash, so we need to keep track of the number of operations
204+
// that are in-flight. If the answer is more than one, throw a managed exception.
205+
private int _callOperations;
206+
201207
public HmacHashProvider(string hashAlgorithmId, ReadOnlySpan<byte> key)
202208
{
203209
_liteHmac = LiteHashProvider.CreateHmac(hashAlgorithmId, key);
204210
}
205211

206212
public override void AppendHashData(ReadOnlySpan<byte> data)
207213
{
208-
_liteHmac.Append(data);
209-
_running = true;
214+
try
215+
{
216+
if (Interlocked.Increment(ref _callOperations) != 1)
217+
{
218+
throw GetConcurrentUseException();
219+
}
220+
221+
_liteHmac.Append(data);
222+
_running = true;
223+
}
224+
finally
225+
{
226+
Interlocked.Decrement(ref _callOperations);
227+
}
210228
}
211229

212230
public override int FinalizeHashAndReset(Span<byte> destination)
213231
{
214-
int written = _liteHmac.Finalize(destination);
215-
_liteHmac.Reset();
216-
_running = false;
217-
return written;
232+
try
233+
{
234+
if (Interlocked.Increment(ref _callOperations) != 1)
235+
{
236+
throw GetConcurrentUseException();
237+
}
238+
239+
int written = _liteHmac.Finalize(destination);
240+
_liteHmac.Reset();
241+
_running = false;
242+
return written;
243+
}
244+
finally
245+
{
246+
Interlocked.Decrement(ref _callOperations);
247+
}
218248
}
219249

220250
public override int GetCurrentHash(Span<byte> destination)
221251
{
222-
return _liteHmac.Current(destination);
252+
try
253+
{
254+
if (Interlocked.Increment(ref _callOperations) != 1)
255+
{
256+
throw GetConcurrentUseException();
257+
}
258+
259+
return _liteHmac.Current(destination);
260+
}
261+
finally
262+
{
263+
Interlocked.Decrement(ref _callOperations);
264+
}
223265
}
224266

225267
public override int HashSizeInBytes => _liteHmac.HashSizeInBytes;
226268

227269
public override void Dispose(bool disposing)
228270
{
271+
// We don't need to worry about the _callOperations here because the SafeHandle takes care of reference
272+
// counting the dispose.
273+
229274
if (disposing)
230275
{
231276
_liteHmac.Dispose();
@@ -234,12 +279,27 @@ public override void Dispose(bool disposing)
234279

235280
public override void Reset()
236281
{
237-
if (_running)
282+
try
238283
{
239-
_liteHmac.Reset();
240-
_running = false;
284+
if (Interlocked.Increment(ref _callOperations) != 1)
285+
{
286+
throw GetConcurrentUseException();
287+
}
288+
289+
if (_running)
290+
{
291+
_liteHmac.Reset();
292+
_running = false;
293+
}
294+
}
295+
finally
296+
{
297+
Interlocked.Decrement(ref _callOperations);
241298
}
242299
}
300+
301+
private static CryptographicException GetConcurrentUseException() =>
302+
new CryptographicException(SR.Cryptography_ConcurrentUseNotSupported);
243303
}
244304
}
245305
}

src/libraries/System.Security.Cryptography/tests/IncrementalHashTests.cs

+102
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Collections.Generic;
5+
using System.Threading;
6+
using Microsoft.DotNet.RemoteExecutor;
57
using Test.Cryptography;
68
using Xunit;
79

@@ -622,6 +624,106 @@ public static void VerifyBounds_GetHashAndReset_Hash(HashAlgorithm referenceAlgo
622624
}
623625
}
624626

627+
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
628+
public static void Hash_GetHashAndReset_ConcurrentUseDoesNotCrashProcess()
629+
{
630+
static void ThreadWork(object obj)
631+
{
632+
try
633+
{
634+
IncrementalHash hash = (IncrementalHash)obj;
635+
636+
for (int i = 0; i < 10_000; i++)
637+
{
638+
hash.AppendData("potatos and carrots make for a fine stew."u8);
639+
hash.GetHashAndReset();
640+
}
641+
}
642+
catch
643+
{
644+
// Ignore all managed exceptions. IncrementalHash is not thread safe, but we don't want process
645+
// crashes.
646+
}
647+
}
648+
649+
RemoteExecutor.Invoke(static () =>
650+
{
651+
foreach(object[] items in GetHashAlgorithms())
652+
{
653+
if (items is [HashAlgorithm referenceAlgorithm, HashAlgorithmName hashAlgorithm])
654+
{
655+
referenceAlgorithm.Dispose();
656+
657+
using (IncrementalHash hash = IncrementalHash.CreateHash(hashAlgorithm))
658+
{
659+
Thread thread1 = new(ThreadWork);
660+
Thread thread2 = new(ThreadWork);
661+
thread1.Start(hash);
662+
thread2.Start(hash);
663+
thread1.Join();
664+
thread2.Join();
665+
}
666+
}
667+
else
668+
{
669+
Assert.Fail("Test is not set up correctly.");
670+
}
671+
}
672+
673+
return RemoteExecutor.SuccessExitCode;
674+
}).Dispose();
675+
}
676+
677+
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
678+
public static void HMAC_GetHashAndReset_ConcurrentUseDoesNotCrashProcess()
679+
{
680+
static void ThreadWork(object obj)
681+
{
682+
try
683+
{
684+
IncrementalHash hash = (IncrementalHash)obj;
685+
686+
for (int i = 0; i < 10_000; i++)
687+
{
688+
hash.AppendData("potatos and carrots make for a fine stew."u8);
689+
hash.GetHashAndReset();
690+
}
691+
}
692+
catch
693+
{
694+
// Ignore all managed exceptions. IncrementalHash is not thread safe, but we don't want process
695+
// crashes.
696+
}
697+
}
698+
699+
RemoteExecutor.Invoke(static () =>
700+
{
701+
foreach(object[] items in GetHMACs())
702+
{
703+
if (items is [HashAlgorithm referenceAlgorithm, HashAlgorithmName hashAlgorithm])
704+
{
705+
referenceAlgorithm.Dispose();
706+
707+
using (IncrementalHash hash = IncrementalHash.CreateHMAC(hashAlgorithm, [1, 2, 3, 4]))
708+
{
709+
Thread thread1 = new(ThreadWork);
710+
Thread thread2 = new(ThreadWork);
711+
thread1.Start(hash);
712+
thread2.Start(hash);
713+
thread1.Join();
714+
thread2.Join();
715+
}
716+
}
717+
else
718+
{
719+
Assert.Fail("Test is not set up correctly.");
720+
}
721+
}
722+
723+
return RemoteExecutor.SuccessExitCode;
724+
}).Dispose();
725+
}
726+
625727
private static void VerifyGetCurrentHash(IncrementalHash single, IncrementalHash accumulated)
626728
{
627729
Span<byte> buf = stackalloc byte[2048];

0 commit comments

Comments
 (0)