Repository: dotnet/runtime
File: src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs (C#)

Code before:

                return toCopy;
            }
            return -1;
        }

        //
        // This method assumes that a SSPI context is already in a good shape.
        // For example it is either a fresh context or already authenticated context that needs renegotiation.
        //
        private Task ProcessAuthentication(bool isAsync = false, bool isApm = false, CancellationToken cancellationToken = default)
        {
            Task result = null;
            if (Interlocked.Exchange(ref _nestedAuth, 1) == 1)
            {
                throw new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, isApm ? "BeginAuthenticate" : "Authenticate", "authenticate"));
            }

            try
            {
                ThrowIfExceptional();

                if (isAsync)
                {
                    result = ForceAuthenticationAsync(new SslAsyncIO(this, cancellationToken), _context.IsServer, null);
                    result.ContinueWith(t => { _nestedAuth = 0; }, cancellationToken,
                                         TaskContinuationOptions.NotOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously,
                                         TaskScheduler.Default);
                }
                else
                {
                    ForceAuthenticationAsync(new SslSyncIO(this), _context.IsServer, null).GetAwaiter().GetResult();
                }
            }
            finally
            {
                if (!isAsync || result.IsCompleted)
                {
                    // Operation has completed. (successfully or failed)
                    _nestedAuth = 0;
                }
            }

            return result;
        }

        //
        // This is used to reply on re-handshake when received SEC_I_RENEGOTIATE on Read().
        //
        private async Task ReplyOnReAuthenticationAsync<TReadAdapter>(TReadAdapter adapter, byte[] buffer)
            where TReadAdapter : ISslIOAdapter

Review comment (bug):
Why is this continuation needed?  We can't set _nestedAuth to 0 in the ForCeAuthenticationAsync implementation as we were previously doing?

I do like that this approach centralizes the responsibility for zeroing it out here. However, it adds cost, and as written I'm also not 100% convinced it's correct.  We're going to end up setting _nestedAuth to 0 potentially twice, when there's a race condition where this result task completes unsuccessfully by the time we hit the finally block below.

But worse, technically I think this is introducing a race condition where consumers could end up erroneously getting an error that an operation was still in progress even if one wasn't.  The problem is that _nestedAuth is being set to 0 _after_ the result task completes, and it's this result task that's being returned to the caller.  So technically a caller could see that the task completed and go to start their next operation, only to find that _nestedAuth hadn't yet been zeroed.

For this to be correct, we really need to make it so that the returned task doesn't complete until after _nestedAuth is reset.

Code after:
        {
            lock (SyncLock)
            {
                // Note we are already inside the read, so checking for already going concurrent handshake.
                _lockReadState = LockHandshake;
            }

            await ForceAuthenticationAsync(adapter, receiveFirst: false, buffer).ConfigureAwait(false);
            FinishHandshakeRead(LockNone);
        }

        // reAuthenticationData is only used on Windows in case of renegotiation.
        private async Task ForceAuthenticationAsync<TIOAdapter>(TIOAdapter adapter, bool receiveFirst, byte[] reAuthenticationData, bool isApm = false)
             where TIOAdapter : ISslIOAdapter
        {
            _framing = Framing.Unknown;
            ProtocolToken message;

            if (reAuthenticationData == null)
            {
                // prevent nesting only when authentication functions are called explicitly. e.g. handle renegotiation tansparently.
                if (Interlocked.Exchange(ref _nestedAuth, 1) == 1)
                {
                    throw new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, isApm ? "BeginAuthenticate" : "Authenticate", "authenticate"));
                }
            }

            try
            {

                if (!receiveFirst)
                {
                    message = _context.NextMessage(reAuthenticationData, 0, (reAuthenticationData == null ? 0 : reAuthenticationData.Length));
                    if (message.Size > 0)
                    {
                        await adapter.WriteAsync(message.Payload, 0, message.Size).ConfigureAwait(false);
                    }

                    if (message.Failed)
                    {
                        // tracing done in NextMessage()
                        throw new AuthenticationException(SR.net_auth_SSPI, message.GetException());
                    }
                }

                do
                {
                    message = await ReceiveBlobAsync(adapter).ConfigureAwait(false);
                    if (message.Size > 0)
                    {
                        // If there is message send it out even if call failed. It may contain TLS Alert.