diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs index 45ea9a02e0745..2cab7ef307116 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs @@ -362,7 +362,7 @@ private async ValueTask ReadAsync(TAdapter adapter, Memory while (true) { - int readBytes = await ReadAllAsync(adapter, _readHeader).ConfigureAwait(false); + int readBytes = await ReadAllAsync(adapter, _readHeader, allowZeroRead: true).ConfigureAwait(false); if (readBytes == 0) { return 0; @@ -386,12 +386,8 @@ private async ValueTask ReadAsync(TAdapter adapter, Memory { _readBuffer = new byte[readBytes]; } - readBytes = await ReadAllAsync(adapter, new Memory(_readBuffer, 0, readBytes)).ConfigureAwait(false); - if (readBytes == 0) - { - // We already checked that the frame body is bigger than 0 bytes. Hence, this is an EOF. - throw new IOException(SR.net_io_eof); - } + + readBytes = await ReadAllAsync(adapter, new Memory(_readBuffer, 0, readBytes), allowZeroRead: false).ConfigureAwait(false); // Decrypt into internal buffer, change "readBytes" to count now _Decrypted Bytes_ // Decrypted data start from zero offset, the size can be shrunk after decryption. @@ -423,16 +419,16 @@ private async ValueTask ReadAsync(TAdapter adapter, Memory _readInProgress = 0; } - static async ValueTask ReadAllAsync(TAdapter adapter, Memory buffer) + static async ValueTask ReadAllAsync(TAdapter adapter, Memory buffer, bool allowZeroRead) { - int length = buffer.Length; + int read = 0; do { int bytes = await adapter.ReadAsync(buffer).ConfigureAwait(false); if (bytes == 0) { - if (!buffer.IsEmpty) + if (read != 0 || !allowZeroRead) { throw new IOException(SR.net_io_eof); } @@ -440,10 +436,11 @@ static async ValueTask ReadAllAsync(TAdapter adapter, Memory buffer) } buffer = buffer.Slice(bytes); + read += bytes; } while (!buffer.IsEmpty); - return length; + return read; } } diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/NegotiateStreamStreamToStreamTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/NegotiateStreamStreamToStreamTest.cs index 9c674b77594a3..62a0bf76ad303 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/NegotiateStreamStreamToStreamTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/NegotiateStreamStreamToStreamTest.cs @@ -376,6 +376,29 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout( await Assert.ThrowsAnyAsync(() => t); } } + + [ConditionalFact(nameof(IsNtlmInstalled))] + public async Task NegotiateStream_ReadToEof_Returns0() + { + (Stream stream1, Stream stream2) = TestHelper.GetConnectedTcpStreams(); + using (var client = new NegotiateStream(stream1)) + using (var server = new NegotiateStream(stream2)) + { + await TestConfiguration.WhenAllOrAnyFailedWithTimeout( + AuthenticateAsClientAsync(client, CredentialCache.DefaultNetworkCredentials, string.Empty), + AuthenticateAsServerAsync(server)); + + client.Write(Encoding.UTF8.GetBytes("hello")); + client.Dispose(); + + Assert.Equal('h', server.ReadByte()); + Assert.Equal('e', server.ReadByte()); + Assert.Equal('l', server.ReadByte()); + Assert.Equal('l', server.ReadByte()); + Assert.Equal('o', server.ReadByte()); + Assert.Equal(-1, server.ReadByte()); + } + } } public sealed class NegotiateStreamStreamToStreamTest_Async_Array : NegotiateStreamStreamToStreamTest