Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make sure failed SSL does not impact other sessions #64256

Merged
merged 5 commits into from
Feb 4, 2022

Conversation

wfurt
Copy link
Member

@wfurt wfurt commented Jan 25, 2022

I was finally able to reproduce it while looking at something else. I have debug build of OpenSSL + custom tracing and that allowed me to have somewhat reliable repro (to fail on 10-15 minutes)

Here is what I saw:
Some test can Dispose SslStream while still in handshake. When this happens, The handle is released by the interior code as soon as we exit from the pinvoke.

  at Microsoft.Win32.SafeHandles.SafeSslHandle.ReleaseHandle() in /home/furt/github/wfurt-runtime/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs:line 460
   at System.Runtime.InteropServices.SafeHandle.InternalRelease(Boolean disposeOrFinalizeOperation) in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeHandle.cs:line 249
   at Interop.Ssl.SslDoHandshake(SafeSslHandle ssl) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Security/src/Microsoft.Interop.DllImportGenerator/Microsoft.Interop.DllImportGenerator/GeneratedDllImports.g.cs:line 3276
   at Interop.OpenSsl.DoSslHandshake(SafeSslHandle context, ReadOnlySpan`1 input, Byte[]& sendBuf, Int32& sendCount) in /home/furt/github/wfurt-runtime/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs:line 448
   at System.Net.Security.SslStreamPal.HandshakeInternal(SafeFreeCredentials credential, SafeDeleteSslContext& context, ReadOnlySpan`1 inputBuffer, Byte[]& outputBuffer, SslAuthenticationOptions sslAuthenticationOptions) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs:line 162
   at System.Net.Security.SslStreamPal.InitializeSecurityContext(SafeFreeCredentials& credential, SafeDeleteSslContext& context, String targetName, ReadOnlySpan`1 inputBuffer, Byte[]& outputBuffer, SslAuthenticationOptions sslAuthenticationOptions) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs:line 36
   at System.Net.Security.SecureChannel.GenerateToken(ReadOnlySpan`1 inputBuffer, Byte[]& output) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs:line 808
   at System.Net.Security.SecureChannel.NextMessage(ReadOnlySpan`1 incomingBuffer) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs:line 730
   at System.Net.Security.SslStream.ProcessBlob(Int32 frameSize) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs:line 614
   at System.Net.Security.SslStream.ReceiveBlobAsync[TIOAdapter](TIOAdapter adapter) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs:line 576
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs:line 183
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext(Thread threadPoolThread) in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs:line 324
   at System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(IAsyncStateMachineBox box, Boolean allowInlining) in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskContinuation.cs:line 795
   at System.Threading.Tasks.Task.RunContinuations(Object continuationObject) in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs:line 3374
   at System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder.SetResult() in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs:line 41
   at System.Net.Security.SslStream.<FillHandshakeBufferAsync>g__InternalFillHandshakeBufferAsync|189_0[TIOAdapter](TIOAdapter adap, ValueTask`1 task, Int32 minSize) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs:line 1133
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs:line 183
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext(Thread threadPoolThread) in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs:line 324
   at System.Threading.ThreadPool.<>c.<.cctor>b__86_0(Object state) in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs:line 1064
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.InvokeContinuation(Action`1 continuation, Object state, Boolean forceAsync, Boolean requiresExecutionContextFlow) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs:line 1352
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.OnCompleted(SocketAsyncEventArgs _) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs:line 1020
   at System.Net.Sockets.SocketAsyncEventArgs.OnCompletedInternal() in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.cs:line 206
   at System.Net.Sockets.SocketAsyncEventArgs.FinishOperationAsyncSuccess(Int32 bytesTransferred, SocketFlags flags) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.cs:line 981
   at System.Net.Sockets.SocketAsyncEventArgs.CompletionCallback(Int32 bytesTransferred, SocketFlags flags, SocketError socketError) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Unix.cs:line 383
   at System.Net.Sockets.SocketAsyncEventArgs.TransferCompletionCallbackCore(Int32 bytesTransferred, Byte[] socketAddress, Int32 socketAddressSize, SocketFlags receivedFlags, SocketError socketError) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Unix.cs:line 110
   at System.Net.Sockets.SocketAsyncContext.BufferMemoryReceiveOperation.InvokeCallback(Boolean allowPooling) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs:line 500
   at System.Net.Sockets.SocketAsyncContext.OperationQueue`1.ProcessAsyncOperation(TOperation op) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs:line 973
   at System.Net.Sockets.SocketAsyncContext.ProcessAsyncReadOperation(ReadOperation op) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs:line 1382
   at System.Net.Sockets.SocketAsyncContext.ReadOperation.System.Threading.IThreadPoolWorkItem.Execute() in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs:line 341
   at System.Net.Sockets.SocketAsyncContext.AsyncOperation.Process() in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs:line 292
   at System.Net.Sockets.SocketAsyncContext.HandleEvents(SocketEvents events) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs:line 2187
   at System.Net.Sockets.SocketAsyncEngine.System.Threading.IThreadPoolWorkItem.Execute() in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs:line 242
   at System.Threading.ThreadPoolWorkQueue.Dispatch() in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs:line 715
   at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart() in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs:line 77
   at System.Threading.Thread.StartCallback() in /home/furt/github/wfurt-runtime/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs:line 105

then handshake function reruns failure and we will try to get more info about it. However, because the handle is closed and released that fails

System.ObjectDisposedException: Safe handle has been closed.
Object name: 'SafeHandle'.
   at System.Runtime.InteropServices.SafeHandle.DangerousAddRef(Boolean& success) in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeHandle.cs:line 150
   at Interop.Ssl.SslGetError(SafeSslHandle ssl, Int32 ret) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Security/src/Microsoft.Interop.DllImportGenerator/Microsoft.Interop.DllImportGenerator/GeneratedDllImports.g.cs:line 2601
   at Interop.OpenSsl.GetSslError(SafeSslHandle context, Int32 result, Exception& innerError) in /home/furt/github/wfurt-runtime/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs:line 934
   at Interop.OpenSsl.DoSslHandshake(SafeSslHandle context, ReadOnlySpan`1 input, Byte[]& sendBuf, Int32& sendCount) in /home/furt/github/wfurt-runtime/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs:line 453
   at System.Net.Security.SslStreamPal.HandshakeInternal(SafeFreeCredentials credential, SafeDeleteSslContext& context, ReadOnlySpan`1 inputBuffer, Byte[]& outputBuffer, SslAuthenticationOptions sslAuthenticationOptions) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs:line 162

so we throw and that bails out of the handshake leaving the error behind. The OpenSSL error queue is per-thread so when unrelated operation comes in and if there is debug build we hit the assert - that guards agains exactly this behavior. On release build we can mix the errors and return back something lame.

It is somewhat unfortunate that with current logic, basically any call to OpenSSL with handle can throw.
My First fix was to add DangerousAddRef/Release to keep the handle always alive through the handshake.
I did ~ 1000 runs over night and never hit a problem.

Eventually I decided it is OK to throw and I added finally block to clean up the error.
We may not need the cleanup if handshake finished successfully and we were able to get the error.
But it its probably cheap enough to just keep it there as common case.

We may explore how to use the handles and avoid cases like this but for now I decided to go with simplest fix as we see hits in CI and it is unpleasant as it kills whole set run.

fixes #57722

@wfurt wfurt added area-System.Net.Security os-linux Linux OS (any supported distro) labels Jan 25, 2022
@wfurt wfurt added this to the 7.0.0 milestone Jan 25, 2022
@wfurt wfurt requested review from stephentoub and a team January 25, 2022 04:50
@wfurt wfurt self-assigned this Jan 25, 2022
@ghost
Copy link

ghost commented Jan 25, 2022

Tagging subscribers to this area: @dotnet/ncl, @vcsjones
See info in area-owners.md if you want to be subscribed.

Issue Details

I was finally able to reproduce it while looking at something else. I have debug build of OpenSSL + custom tracing and that allowed me to have somewhat reliable repro (to fail on 10-15 minutes)

Here is what I saw:
Some test can Dispose SslStream while still in handshake. When this happens, The handle is released by the interior code as soon as we exit from the pinvoke.

  at Microsoft.Win32.SafeHandles.SafeSslHandle.ReleaseHandle() in /home/furt/github/wfurt-runtime/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs:line 460
   at System.Runtime.InteropServices.SafeHandle.InternalRelease(Boolean disposeOrFinalizeOperation) in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeHandle.cs:line 249
   at Interop.Ssl.SslDoHandshake(SafeSslHandle ssl) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Security/src/Microsoft.Interop.DllImportGenerator/Microsoft.Interop.DllImportGenerator/GeneratedDllImports.g.cs:line 3276
   at Interop.OpenSsl.DoSslHandshake(SafeSslHandle context, ReadOnlySpan`1 input, Byte[]& sendBuf, Int32& sendCount) in /home/furt/github/wfurt-runtime/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs:line 448
   at System.Net.Security.SslStreamPal.HandshakeInternal(SafeFreeCredentials credential, SafeDeleteSslContext& context, ReadOnlySpan`1 inputBuffer, Byte[]& outputBuffer, SslAuthenticationOptions sslAuthenticationOptions) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs:line 162
   at System.Net.Security.SslStreamPal.InitializeSecurityContext(SafeFreeCredentials& credential, SafeDeleteSslContext& context, String targetName, ReadOnlySpan`1 inputBuffer, Byte[]& outputBuffer, SslAuthenticationOptions sslAuthenticationOptions) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs:line 36
   at System.Net.Security.SecureChannel.GenerateToken(ReadOnlySpan`1 inputBuffer, Byte[]& output) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs:line 808
   at System.Net.Security.SecureChannel.NextMessage(ReadOnlySpan`1 incomingBuffer) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs:line 730
   at System.Net.Security.SslStream.ProcessBlob(Int32 frameSize) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs:line 614
   at System.Net.Security.SslStream.ReceiveBlobAsync[TIOAdapter](TIOAdapter adapter) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs:line 576
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs:line 183
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext(Thread threadPoolThread) in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs:line 324
   at System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(IAsyncStateMachineBox box, Boolean allowInlining) in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskContinuation.cs:line 795
   at System.Threading.Tasks.Task.RunContinuations(Object continuationObject) in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs:line 3374
   at System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder.SetResult() in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs:line 41
   at System.Net.Security.SslStream.<FillHandshakeBufferAsync>g__InternalFillHandshakeBufferAsync|189_0[TIOAdapter](TIOAdapter adap, ValueTask`1 task, Int32 minSize) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs:line 1133
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs:line 183
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext(Thread threadPoolThread) in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs:line 324
   at System.Threading.ThreadPool.<>c.<.cctor>b__86_0(Object state) in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs:line 1064
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.InvokeContinuation(Action`1 continuation, Object state, Boolean forceAsync, Boolean requiresExecutionContextFlow) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs:line 1352
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.OnCompleted(SocketAsyncEventArgs _) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs:line 1020
   at System.Net.Sockets.SocketAsyncEventArgs.OnCompletedInternal() in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.cs:line 206
   at System.Net.Sockets.SocketAsyncEventArgs.FinishOperationAsyncSuccess(Int32 bytesTransferred, SocketFlags flags) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.cs:line 981
   at System.Net.Sockets.SocketAsyncEventArgs.CompletionCallback(Int32 bytesTransferred, SocketFlags flags, SocketError socketError) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Unix.cs:line 383
   at System.Net.Sockets.SocketAsyncEventArgs.TransferCompletionCallbackCore(Int32 bytesTransferred, Byte[] socketAddress, Int32 socketAddressSize, SocketFlags receivedFlags, SocketError socketError) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Unix.cs:line 110
   at System.Net.Sockets.SocketAsyncContext.BufferMemoryReceiveOperation.InvokeCallback(Boolean allowPooling) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs:line 500
   at System.Net.Sockets.SocketAsyncContext.OperationQueue`1.ProcessAsyncOperation(TOperation op) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs:line 973
   at System.Net.Sockets.SocketAsyncContext.ProcessAsyncReadOperation(ReadOperation op) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs:line 1382
   at System.Net.Sockets.SocketAsyncContext.ReadOperation.System.Threading.IThreadPoolWorkItem.Execute() in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs:line 341
   at System.Net.Sockets.SocketAsyncContext.AsyncOperation.Process() in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs:line 292
   at System.Net.Sockets.SocketAsyncContext.HandleEvents(SocketEvents events) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs:line 2187
   at System.Net.Sockets.SocketAsyncEngine.System.Threading.IThreadPoolWorkItem.Execute() in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs:line 242
   at System.Threading.ThreadPoolWorkQueue.Dispatch() in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs:line 715
   at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart() in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs:line 77
   at System.Threading.Thread.StartCallback() in /home/furt/github/wfurt-runtime/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs:line 105

then handshake function reruns failure and we will try to get more info about it. However, because the handle is closed and released that fails

System.ObjectDisposedException: Safe handle has been closed.
Object name: 'SafeHandle'.
   at System.Runtime.InteropServices.SafeHandle.DangerousAddRef(Boolean& success) in /home/furt/github/wfurt-runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeHandle.cs:line 150
   at Interop.Ssl.SslGetError(SafeSslHandle ssl, Int32 ret) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Security/src/Microsoft.Interop.DllImportGenerator/Microsoft.Interop.DllImportGenerator/GeneratedDllImports.g.cs:line 2601
   at Interop.OpenSsl.GetSslError(SafeSslHandle context, Int32 result, Exception& innerError) in /home/furt/github/wfurt-runtime/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs:line 934
   at Interop.OpenSsl.DoSslHandshake(SafeSslHandle context, ReadOnlySpan`1 input, Byte[]& sendBuf, Int32& sendCount) in /home/furt/github/wfurt-runtime/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs:line 453
   at System.Net.Security.SslStreamPal.HandshakeInternal(SafeFreeCredentials credential, SafeDeleteSslContext& context, ReadOnlySpan`1 inputBuffer, Byte[]& outputBuffer, SslAuthenticationOptions sslAuthenticationOptions) in /home/furt/github/wfurt-runtime/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs:line 162

so we throw and that bails out of the handshake leaving the error behind. The OpenSSL error queue is per-thread so when unrelated operation comes in and if there is debug build we hit the assert - that guards agains exactly this behavior. On release build we can mix the errors and return back something lame.

It is somewhat unfortunate that with current logic, basically any call to OpenSSL with handle can throw.
My First fix was to add DangerousAddRef/Release to keep the handle always alive through the handshake.
I did ~ 1000 runs over night and never hit a problem.

Eventually I decided it is OK to throw and I added finally block to clean up the error.
We may not need the cleanup if handshake finished successfully and we were able to get the error.
But it its probably cheap enough to just keep it there as common case.

We may explore how to use the handles and avoid cases like this but for now I decided to go with simplest fix as we see hits in CI and it is unpleasant as it kills whole set run.

fixes #57722

Author: wfurt
Assignees: wfurt
Labels:

area-System.Net.Security, os-linux

Milestone: 7.0.0

}
}
finally
{
Crypto.ErrClearError();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment would be helpful.

And there's no finer-grained place to do this? Just seems a little bandaid-y to do this here, as it seems like we don't know where the extraneous error is coming from.

But if this is the best place, so be it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, we know exactly what is failing - at least in this particular case.

139940077336320:error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version:ssl/record/rec_layer_s3.c:1543:SSL alert number 70

The test is constructed to fail and sends TLS alert. But the client side is disposed before it is able to fully process it.
It should be sufficient to cleanup only on error or exception.

The other aspect is that GetSslError() gets last error from OpenSSL. I don't tank if it is ever possible to get multiple errors. From that prospective Crypto.ErrClearError is generic bandaid for something that may (or may not) happen.

We could avoid this by grabbing reference on the SafeHandle. If we do, we would do handshake AND have ability to query error and follow the error path.

One more thought, Since the Ssl.SslDoHandshake is only called from here, we could change it to pass back also what ever GetSslError() would do. e.g. avoiding the second call that may throw.

Int Ssl.SslDoHandshake(SafeHandle context, ref Ssl.SslErrorCode error)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the PAL method to combine two P/Invokes into one by making the error code an out/ref/pointer parameter, if they're always called in pairs, seems like some goodness (it'll reduce the interlocked counting on the safehandle, and all the other work that goes on in a P/Invoke).

As for sprinkling in a call to ERR_clear_error(): I'm actively working on a change to reverse all of these flows. Every shim function that has a possibility of changing the error queue starts off with ERR_clear_error(). If there are multiple places that it might've sprinkled in noise, it'll call it multiple times.

Once that's done, it should be the case that we can remove the shim function for it altogether, because every flow looks like

  • Clean queue
  • Do work.
  • Execption? Throw first error from the queue.
  • No exception? Queue floats.
  • Next op starts
  • Clear error queue.
  • Do work.
  • ...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll make the minimal change and then I'll leave the general cleanup to you @bartonjs. It seems like failure can put multiple error on queue, right? e.g. just calling GetError() to get last one may not be sufficient...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e.g. just calling GetError() to get last one may not be sufficient...

The new model is to throw the first one (clearing out the rest). The reason the current/"old" model is the last one is because we ignored the queue and let it float around, so the last one is the only one we knew was relevant. But, we've since learned of operations that emit multiple errors, the latter ones being bad error state recovery from the initial.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I modified SslDoHandshake to give error back to avoid extra call and removed the try/finally.
That should be sufficient to deal with the released handle during handshake.

@wfurt
Copy link
Member Author

wfurt commented Feb 3, 2022

I was thinking about this for a while. I agree with @stephentoub that the fix looks bandaid-y and I could scope it more to just cover the case I was able to reproduce.

cc: @bartonjs and @am11 since similar topic bubbled up on separate thread. (not sure if related)

@wfurt wfurt merged commit 02ac5c3 into dotnet:main Feb 4, 2022
@wfurt wfurt deleted the sslError_57722 branch February 4, 2022 18:52
@ghost ghost locked as resolved and limited conversation to collaborators Mar 6, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Net.Security os-linux Linux OS (any supported distro)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Assertion failed: OpenSsl error queue is not empty
4 participants