diff --git a/eng/Versions.props b/eng/Versions.props index 1f6da59091f96c..9394929d56e9e6 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -233,7 +233,7 @@ 8.0.0-rtm.26407.4 - 2.5.9 + 2.5.10 16.0.5-alpha.1.25311.1 16.0.5-alpha.1.25311.1 diff --git a/src/coreclr/vm/eventing/eventpipe/ds-rt-coreclr.h b/src/coreclr/vm/eventing/eventpipe/ds-rt-coreclr.h index 57f5c125742fab..869e9ba9d2ecf0 100644 --- a/src/coreclr/vm/eventing/eventpipe/ds-rt-coreclr.h +++ b/src/coreclr/vm/eventing/eventpipe/ds-rt-coreclr.h @@ -231,9 +231,12 @@ ds_rt_transport_get_default_name ( STATIC_CONTRACT_NOTHROW; #ifdef TARGET_UNIX + // PAL_GetTransportName returns void, but sets name[0] to '\0' when it fails to generate a name. PAL_GetTransportName (name_len, name, prefix, id, group_id, suffix); + return name [0] != '\0'; +#else + return false; #endif - return true; } /* diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/Managed/HttpListenerRequest.Managed.cs b/src/libraries/System.Net.HttpListener/src/System/Net/Managed/HttpListenerRequest.Managed.cs index 57deaec43a6d8e..a9e0bca950d902 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/Managed/HttpListenerRequest.Managed.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/Managed/HttpListenerRequest.Managed.cs @@ -219,15 +219,9 @@ internal void AddHeader(string header) string val = header.AsSpan(colon + 1).Trim().ToString(); if (name.Equals("content-length", StringComparison.OrdinalIgnoreCase)) { - // To match Windows behavior: - // Content lengths >= 0 and <= long.MaxValue are accepted as is. - // Content lengths > long.MaxValue and <= ulong.MaxValue are treated as 0. - // Content lengths < 0 cause the requests to fail. - // Other input is a failure, too. - long parsedContentLength = - ulong.TryParse(val, out ulong parsedUlongContentLength) ? (parsedUlongContentLength <= long.MaxValue ? (long)parsedUlongContentLength : 0) : - long.Parse(val); - if (parsedContentLength < 0 || (_clSet && parsedContentLength != _contentLength)) + // Match the Windows parser shape: strict decimal parsing, and reject on parse failure. + bool success = long.TryParse(val, NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out long parsedContentLength); + if (!success || (_clSet && parsedContentLength != _contentLength)) { _context.ErrorMessage = "Invalid Content-Length."; } diff --git a/src/libraries/System.Net.HttpListener/tests/HttpListenerRequestTests.cs b/src/libraries/System.Net.HttpListener/tests/HttpListenerRequestTests.cs index 1f2057a9480536..10b6f9e80244b9 100644 --- a/src/libraries/System.Net.HttpListener/tests/HttpListenerRequestTests.cs +++ b/src/libraries/System.Net.HttpListener/tests/HttpListenerRequestTests.cs @@ -126,8 +126,6 @@ public async Task ContentEncoding_NoBody_ReturnsDefault() [Theory] [InlineData("POST", "Content-Length: 9223372036854775807", 9223372036854775807, true)] // long.MaxValue - [InlineData("POST", "Content-Length: 9223372036854775808", 0, false)] // long.MaxValue + 1 - [InlineData("POST", "Content-Length: 18446744073709551615 ", 0, false)] // ulong.MaxValue [InlineData("POST", "Content-Length: 0", 0, false)] [InlineData("PUT", "Content-Length: 0", 0, false)] [InlineData("PUT", "Content-Length: 1", 1, true)] diff --git a/src/libraries/System.Net.HttpListener/tests/InvalidClientRequestTests.cs b/src/libraries/System.Net.HttpListener/tests/InvalidClientRequestTests.cs index 739c5b2711f345..76732e7c918bf3 100644 --- a/src/libraries/System.Net.HttpListener/tests/InvalidClientRequestTests.cs +++ b/src/libraries/System.Net.HttpListener/tests/InvalidClientRequestTests.cs @@ -74,6 +74,8 @@ public static IEnumerable InvalidRequest_TestData() yield return new object[] { "GET {path} HTTP/1.1", null, new string[] { "Content-Length: -9223372036854775809" }, "\r\n", "Bad Request" }; yield return new object[] { "GET {path} HTTP/1.1", null, new string[] { "Content-Length: 1", "Content-Length: 2" }, "\r\n", "Bad Request" }; + yield return new object[] { "POST {path} HTTP/1.1", null, new string[] { "Content-Length: 9223372036854775808" }, "\r\n", "Bad Request" }; // long.MaxValue + 1 + yield return new object[] { "POST {path} HTTP/1.1", null, new string[] { "Content-Length: 18446744073709551615" }, "\r\n", "Bad Request" }; // ulong.MaxValue yield return new object[] { "GET {path} HTTP/1.1", null, new string[] { "Transfer-Encoding: garbage" }, "\r\n", "Not Implemented" }; yield return new object[] { "POST {path} HTTP/1.1", null, new string[] { "Transfer-Encoding: garbage" }, "\r\n", "Not Implemented" }; diff --git a/src/libraries/System.Net.WebSockets/src/Resources/Strings.resx b/src/libraries/System.Net.WebSockets/src/Resources/Strings.resx index fdf7ea01987c61..cf2837576a1f57 100644 --- a/src/libraries/System.Net.WebSockets/src/Resources/Strings.resx +++ b/src/libraries/System.Net.WebSockets/src/Resources/Strings.resx @@ -162,6 +162,9 @@ The message was compressed using an unsupported compression method. + + Data received after the DEFLATE stream was terminated with BFINAL. + The compression options for a continuation cannot be different than the options used to send the first fragment of the message. diff --git a/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/Compression/WebSocketInflater.cs b/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/Compression/WebSocketInflater.cs index 593515cf112a21..b849ca0674ed28 100644 --- a/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/Compression/WebSocketInflater.cs +++ b/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/Compression/WebSocketInflater.cs @@ -127,6 +127,8 @@ public unsafe bool Inflate(Span output, out int written) { _stream ??= CreateInflater(); + bool streamEnded = false; + if (_available > 0 && output.Length > 0) { int consumed; @@ -136,7 +138,7 @@ public unsafe bool Inflate(Span output, out int written) _stream.NextIn = (IntPtr)(bufferPtr + _position); _stream.AvailIn = (uint)_available; - written = Inflate(_stream, output, FlushCode.NoFlush); + written = Inflate(_stream, output, FlushCode.NoFlush, out streamEnded); consumed = _available - (int)_stream.AvailIn; } @@ -154,6 +156,16 @@ public unsafe bool Inflate(Span output, out int written) return _endOfMessage ? Finish(output, ref written) : true; } + if (streamEnded && _available > 0) + { + // zlib reached the end of the DEFLATE stream (a BFINAL=1 final block) while compressed + // bytes still remain that it will never consume. permessage-deflate messages are not + // expected to contain a final block; continuing would make no forward progress (the + // inflater would report empty results forever and hang the caller's receive loop), so + // reject the message. + throw new WebSocketException(SR.net_WebSockets_DataAfterBFinal); + } + return false; } @@ -180,7 +192,7 @@ private unsafe bool Finish(Span output, ref int written) // If we have more space in the output, try to inflate if (output.Length > written) { - written += Inflate(_stream, output[written..], FlushCode.SyncFlush); + written += Inflate(_stream, output[written..], FlushCode.SyncFlush, out _); } // After inflate, if we have more space in the output then it means that we @@ -215,7 +227,7 @@ private static unsafe bool IsFinished(ZLibStreamHandle stream, out byte? remaini // There is no other way to make sure that we've consumed all data // but to try to inflate again with at least one byte of output buffer. byte b; - if (Inflate(stream, new Span(&b, 1), FlushCode.SyncFlush) == 0) + if (Inflate(stream, new Span(&b, 1), FlushCode.SyncFlush, out _) == 0) { remainingByte = null; return true; @@ -225,7 +237,7 @@ private static unsafe bool IsFinished(ZLibStreamHandle stream, out byte? remaini return false; } - private static unsafe int Inflate(ZLibStreamHandle stream, Span destination, FlushCode flushCode) + private static unsafe int Inflate(ZLibStreamHandle stream, Span destination, FlushCode flushCode, out bool streamEnded) { Debug.Assert(destination.Length > 0); ErrorCode errorCode; @@ -239,6 +251,7 @@ private static unsafe int Inflate(ZLibStreamHandle stream, Span destinatio if (errorCode is ErrorCode.Ok or ErrorCode.StreamEnd or ErrorCode.BufError) { + streamEnded = errorCode == ErrorCode.StreamEnd; return destination.Length - (int)stream.AvailOut; } } diff --git a/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.cs b/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.cs index ff4c7935aa2dc2..225efd28abd974 100644 --- a/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.cs +++ b/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.cs @@ -828,7 +828,6 @@ private async ValueTask ReceiveAsyncPrivate(Memory paylo if (_receiveBufferCount > 0) { int receiveBufferBytesToCopy = Math.Min(limit, _receiveBufferCount); - Debug.Assert(receiveBufferBytesToCopy > 0); _receiveBuffer.Span.Slice(_receiveBufferOffset, receiveBufferBytesToCopy).CopyTo( header.Compressed ? _inflater!.Span : payloadBuffer.Span); diff --git a/src/libraries/System.Net.WebSockets/tests/WebSocketDeflateTests.cs b/src/libraries/System.Net.WebSockets/tests/WebSocketDeflateTests.cs index d0fa5bea4a4a5d..99a56fc116cecc 100644 --- a/src/libraries/System.Net.WebSockets/tests/WebSocketDeflateTests.cs +++ b/src/libraries/System.Net.WebSockets/tests/WebSocketDeflateTests.cs @@ -646,6 +646,65 @@ public async Task CompressedMessageWithEmptyLastFrame() Assert.Equal(frame1.Length + frame2.Length, messageSize); } + public static IEnumerable BFinalTerminatedFrames() + { + // A complete (FIN=1) compressed message terminated with a BFINAL=1 final block (decodes + // to "Hello"). 0xf3 sets the BFINAL bit. + yield return new object[] { new byte[] { 0xc1, 0x07, 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x07, 0x00 } }; + + // A non-final (FIN=0) compressed frame whose payload is a BFINAL=1 final block ("Hello") + // followed by trailing bytes that can never be consumed. + yield return new object[] { new byte[] { 0x42, 0x09, 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x07, 0x00, 0x00, 0x00 } }; + } + + [Theory] + [MemberData(nameof(BFinalTerminatedFrames))] + public async Task CompressedMessageWithBFinalBitSet_Throws(byte[] frame) + { + // permessage-deflate messages are not expected to contain a final DEFLATE block. zlib stops + // at the BFINAL=1 block leaving compressed bytes unconsumed, so the message is rejected + // instead of having the inflater spin forever returning empty results. + WebSocketTestStream stream = new(); + stream.Enqueue(frame); + using WebSocket websocket = WebSocket.CreateFromStream(stream, new WebSocketCreationOptions + { + DangerousDeflateOptions = new WebSocketDeflateOptions() + }); + + Memory buffer = new byte[64]; + var exception = await Assert.ThrowsAsync( + async () => await websocket.ReceiveAsync(buffer, CancellationToken)); + Assert.Contains("BFINAL", exception.Message); + Assert.Equal(WebSocketState.Aborted, websocket.State); + } + + [Fact] + public async Task CompressedMessageWithBFinalBitSet_PrecededByValidMessage_Throws() + { + WebSocketTestStream stream = new(); + // A valid sync-flushed message (0xf2, BFINAL not set) decodes successfully... + stream.Enqueue(0xc1, 0x07, 0xf2, 0x48, 0xcd, 0xc9, 0xc9, 0x07, 0x00); + using WebSocket websocket = WebSocket.CreateFromStream(stream, new WebSocketCreationOptions + { + DangerousDeflateOptions = new WebSocketDeflateOptions() + }); + + Memory buffer = new byte[64]; + ValueWebSocketReceiveResult result = await websocket.ReceiveAsync(buffer, CancellationToken); + + Assert.True(result.EndOfMessage); + Assert.Equal("Hello".Length, result.Count); + Assert.Equal(WebSocketMessageType.Text, result.MessageType); + Assert.Equal("Hello", Encoding.UTF8.GetString(buffer.Span.Slice(0, result.Count))); + + // ...but a subsequent message terminated with BFINAL=1 (0xf3) is rejected. + stream.Enqueue(0xc1, 0x07, 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x07, 0x00); + buffer.Span.Clear(); + var exception = await Assert.ThrowsAsync( + async () => await websocket.ReceiveAsync(buffer, CancellationToken)); + Assert.Contains("BFINAL", exception.Message); + } + [Fact] public async Task DisposeShouldNotCorruptStateWhileReceiving() { diff --git a/src/native/eventpipe/ds-ipc-pal-socket.c b/src/native/eventpipe/ds-ipc-pal-socket.c index 7ad0b0f5d4859c..207228c989b178 100644 --- a/src/native/eventpipe/ds-ipc-pal-socket.c +++ b/src/native/eventpipe/ds-ipc-pal-socket.c @@ -715,7 +715,8 @@ ipc_transport_get_default_name ( pd.m_Pid, pd.m_ApplicationGroupId, "socket"); - return true; + // PAL_GetTransportName returns void, but sets name[0] to '\0' when it fails to generate a name. + return name [0] != '\0'; #else return false; #endif @@ -794,7 +795,7 @@ ipc_alloc_uds_address ( EP_ASSERT (ipc != NULL); struct sockaddr_un *server_address = ep_rt_object_alloc (struct sockaddr_un); - ep_return_null_if_nok (server_address != NULL); + ep_raise_error_if_nok (server_address != NULL); server_address->sun_family = AF_UNIX; @@ -804,20 +805,29 @@ ipc_alloc_uds_address ( sizeof (server_address->sun_path), "%s", ipc_name); - if (result <= 0 || result >= (int32_t)(sizeof (server_address->sun_path))) - server_address->sun_path [0] = '\0'; + ep_raise_error_if_nok (result > 0 && result < (int32_t)(sizeof (server_address->sun_path))); } else { // generate the default socket name - ipc_transport_get_default_name ( + ep_raise_error_if_nok (ipc_transport_get_default_name ( server_address->sun_path, - sizeof (server_address->sun_path)); + sizeof (server_address->sun_path))); } + // An empty sun_path would bind to the Linux abstract namespace, which is not supported. + ep_raise_error_if_nok (server_address->sun_path [0] != '\0'); + ipc->server_address = (ds_ipc_socket_address_t *)server_address; ipc->server_address_len = sizeof (struct sockaddr_un); ipc->server_address_family = server_address->sun_family; + server_address = NULL; +ep_on_exit: return ipc; + +ep_on_error: + ep_rt_object_free (server_address); + ipc = NULL; + ep_exit_error_handler (); #else return NULL; #endif