runtime: Crash on WindowsCryptographicException on net60 GA

Description

System.AggregateException: One or more errors occurred. (Unknown error (0xc1000008)) —> Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: Unknown error (0xc1000008) at Internal.Cryptography.HashProviderCng.AppendHashData(ReadOnlySpan`1 source) at Internal.Cryptography.HashProvider.AppendHashData(Byte[] data, Int32 offset, Int32 count) at System.Security.Cryptography.SHA256.Implementation.HashCore(Byte[] array, Int32 ibStart, Int32 cbSize) at System.Security.Cryptography.HashAlgorithm.ComputeHash(Byte[] buffer) at Host.CLI.Program.<>c__DisplayClass9_2.<<BuildAsync>b__3>d.MoveNext() in C:\work\curiosity\curiosity-ai-website\tools\host\Program.cs:line 280 — End of stack trace from previous location — at Host.CLI.Program.BuildAsync(String path, String buildPath, String templatePath, String translationPath) in C:\work\curiosity\curiosity-ai-website\tools\host\Program.cs:line 296 — End of inner exception stack trace — at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) at System.Threading.Tasks.Task.Wait() at Host.CLI.Delayed.<CreateScheduledQueueChecker>g__CheckBacklog|5_1() in C:\work\curiosity\curiosity-ai-website\tools\host\Delayed.cs:line 71 at Host.CLI.Delayed.<>c.<CreateScheduledQueueChecker>b__5_0(Object _) in C:\work\curiosity\curiosity-ai-website\tools\host\Delayed.cs:line 49 at System.Threading.TimerQueueTimer.<>c.<.cctor>b__27_0(Object state) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) — End of stack trace from previous location — at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.TimerQueueTimer.CallCallback(Boolean isThreadPool) at System.Threading.TimerQueueTimer.Fire(Boolean isThreadPool) at System.Threading.TimerQueue.FireNextTimers() at System.Threading.TimerQueue.AppDomainTimerCallback(Int32 id)

Reproduction Steps

The app in question is a simple web host, accessing the page via the edge browser.

Expected behavior

No crash

Actual behavior

Process crashes hard with an uncaught exception

Regression?

Yes. The issue never happen till I installed the net60 GA SDK installed this morning.

Known Workarounds

No response

Configuration

Window x64

dotnet --list-sdks

2.1.526 [C:\Program Files\dotnet\sdk]
2.1.617 [C:\Program Files\dotnet\sdk]
2.1.701 [C:\Program Files\dotnet\sdk]
2.1.818 [C:\Program Files\dotnet\sdk]
2.2.101 [C:\Program Files\dotnet\sdk]
2.2.301 [C:\Program Files\dotnet\sdk]
3.0.100-preview8-013656 [C:\Program Files\dotnet\sdk]
3.0.100 [C:\Program Files\dotnet\sdk]
3.1.200 [C:\Program Files\dotnet\sdk]
3.1.301 [C:\Program Files\dotnet\sdk]
3.1.415 [C:\Program Files\dotnet\sdk]
5.0.104 [C:\Program Files\dotnet\sdk]
5.0.209 [C:\Program Files\dotnet\sdk]
5.0.303 [C:\Program Files\dotnet\sdk]
5.0.403 [C:\Program Files\dotnet\sdk]
6.0.100-rc.1.21458.32 [C:\Program Files\dotnet\sdk]
6.0.100 [C:\Program Files\dotnet\sdk]  <---- App is running on this now

Other information

No response

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 20 (11 by maintainers)

Commits related to this issue

Most upvoted comments

Hi @vcsjones, I’ve not observed the issue anymore! Nice to hear it uncovered another issue nevertheless! Thanks!

The code is approximately this:

var files = new []{ "path to file", ... }; //list of files
await Task.WhenAll(files.Select(async f => await DoSomethingWithFile(f, Sha256.ComputeHash(await File.ReadAllBytesAsync(f)))));

It looks like you are using an instance of SHA256 concurrently. You have a property, field, or local instance named Sha256 that is concurrently (Task.WhenAll) computing the file hashes.

If you are targeting net5.0 or higher, you can change it to this:

var files = new []{ "path to file", ... }; //list of files
await Task.WhenAll(files.Select(async f => await DoSomethingWithFile(f, SHA256.HashData(await File.ReadAllBytesAsync(f)))));

HashData was introduced in .NET 5. It is a static method and it is thread safe.

To round things out here:

  1. The issue reported by @theolivenbaum and @jaylagorio was due to misusing an instance between threads. @theolivenbaum did switching to the static one-shot alleviate your exceptions?
  2. The issue reported by @alanmcgovern was fixed, and has been backported to .NET 6. I believe the fix will be in the 6.0.2 release.

@vcsjones I have a repro case for a similar, if not identical, issue:

This breaks:

var buffer = new byte[1024];
var hasher = SHA1.Create ();
hasher.TransformBlock (buffer, 0, buffer.Length, buffer, 0);
//hasher.TransformFinalBlock (Array.Empty<byte> (), 0, 0);
hasher.Initialize ();
hasher.TransformBlock (buffer, 0, buffer.Length, buffer, 0);
hasher.TransformFinalBlock (Array.Empty<byte> (), 0, 0);
GC.KeepAlive (hasher.Hash);
hasher.Initialize ();
hasher.TransformBlock (buffer, 0, buffer.Length, buffer, 0);

If you want things to break in .NET6 just leave the 4th line commented. If you want it to not break, uncomment line 4.

If you re-initialize a hasher after calling TransformBlock and before calling TransformFinalBlock, you corrupt the hasher and it fails.

EDIT: Thanks to @EvgeniyZ-ru for bringing this issue to my attention, and working with me to provide the information I needed to distill a testcase 😃

A few guesses, since I don’t have a lot to go on.

  1. Assuming it is this project: https://github.com/JKorf/Kraken.Net/
  2. And it’s throwing at this line: https://github.com/JKorf/Kraken.Net/blob/0802f64af669bd77209218517063303750aadc79/Kraken.Net/KrakenAuthenticationProvider.cs#L59

I suspect it is might be because the HMACSHA512 encryptor field is being accessed from multiple threads. Instances of HMACSHA512 are not thread safe.

I can reproduce this exception by using an HMACSHA512 instance between threads:

using System;
using System.Threading;
using System.Security.Cryptography;

using HMACSHA512 hmac = new HMACSHA512();
Thread thread1 = new Thread(static obj => {
    while (true)
    {
        ((HMACSHA512)obj).ComputeHash(new byte[1]);
    }
});
Thread thread2 = new Thread(static obj => {
    while (true)
    {
        ((HMACSHA512)obj).ComputeHash(new byte[1]);
    }
});

thread1.Start(hmac);
thread2.Start(hmac);
thread1.Join();
thread2.Join();

That will quickly give me the same issue as described in the original post.

At this point I can only suggest that you confirm with the library author that no instance of a hash or HMAC algorithm is being used by two or more threads, concurrently.

If that is indeed the case, I would recommend creating a new instance of the hash / HMAC algorithm per-thread, or just always as-needed. Alternatively, using the HMACXYZ.HashData one-shot static methods that were introduced in .NET 5 for digests, and .NET 6 for HMACs.