From b5bc6d048729ae728cc3375025760b429df33d85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1=C5=88a=20P=C3=ADchov=C3=A1?= Date: Wed, 1 Jul 2026 13:05:59 +0000 Subject: [PATCH 1/5] Merged PR 62371: [release/8.0] Fix WebSocked inflater handling of BFinal Fix handling of BFinal in WebSocket deflate. ---- #### AI description (iteration 1) #### PR Classification Bug fix to correct WebSocket inflater handling when DEFLATE streams are terminated with the BFINAL bit set. #### PR Summary This pull request fixes a bug where WebSocket compression handling would hang indefinitely when receiving messages with DEFLATE streams terminated by a BFINAL=1 final block, which violates the permessage-deflate specification. - `WebSocketInflater.cs`: Added detection of DEFLATE stream end (BFINAL=1) and throws `WebSocketException` when compressed bytes remain unconsumed after the stream terminates, preventing infinite loops. - `WebSocketInflater.cs`: Modified `Inflate` method signature to return `streamEnded` flag tracking when zlib encounters `ErrorCode.StreamEnd`. - `WebSocketDeflateTests.cs`: Added comprehensive test cases validating rejection of messages with BFINAL bit set, including both standalone and messages preceded by valid frames. - `Strings.resx`: Added new error message resource `net_WebSockets_DataAfterBFinal` for the exception thrown on invalid BFINAL-terminated messages. --- .../src/Resources/Strings.resx | 3 + .../Compression/WebSocketInflater.cs | 21 +++++-- .../System/Net/WebSockets/ManagedWebSocket.cs | 1 - .../tests/WebSocketDeflateTests.cs | 59 +++++++++++++++++++ 4 files changed, 79 insertions(+), 5 deletions(-) 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() { From e2844c3b693240e0abb0786d548bdfd5cfe17443 Mon Sep 17 00:00:00 2001 From: Irem Yuksel Date: Mon, 13 Jul 2026 19:10:39 +0000 Subject: [PATCH 2/5] Merged PR 62798: [release/8.0] Reject all invalid content lengths in HttpListenerRequest.Managed The check used to set values > long.MaxValue to 0, allowing the communication to continue even though the real size was quite big. This behavior could lead to Content-Length desynchronization. ---- #### AI description (iteration 1) #### PR Classification Bug fix to align managed HttpListener implementation with Windows behavior by rejecting invalid Content-Length header values instead of silently accepting them. #### PR Summary This PR modifies the Content-Length header parsing logic in the managed HttpListener implementation to strictly reject invalid values (including those exceeding long.MaxValue) rather than treating them as valid. The change ensures stricter validation and error handling for malformed HTTP requests. - `HttpListenerRequest.Managed.cs`: Replaced permissive ulong parsing logic with strict long.TryParse using NumberStyles.None, rejecting any Content-Length values that cannot be parsed as valid non-negative long integers - `InvalidClientRequestTests.cs`: Added new test cases verifying that oversized Content-Length values (long.MaxValue+1 and ulong.MaxValue) trigger "Bad Request" errors and prevent context creation - `HttpListenerRequestTests.cs`: Removed test cases that previously expected oversized Content-Length values to be accepted as 0 --- .../Net/Managed/HttpListenerRequest.Managed.cs | 12 +++--------- .../tests/HttpListenerRequestTests.cs | 2 -- .../tests/InvalidClientRequestTests.cs | 2 ++ 3 files changed, 5 insertions(+), 11 deletions(-) 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" }; From 3e849cfecd37b0c9f4d4ffee6083b057ac7c41f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1=C5=88a=20P=C3=ADchov=C3=A1?= Date: Tue, 14 Jul 2026 19:06:54 +0000 Subject: [PATCH 3/5] Merged PR 62894: [release/8.0] [QUIC] Update MsQuic Update MsQuic to the privately built MsQuic 2.5.9 ---- #### AI description (iteration 1) #### PR Classification Dependency update to upgrade the MsQuic library version for QUIC protocol support. #### PR Summary This pull request updates the MsQuic Schannel library from version 2.4.18 to 2.5.9-ci.151956570 in the release/8.0 branch. - `/eng/Versions.props`: Updated `MicrosoftNativeQuicMsQuicSchannelVersion` from 2.4.18 to 2.5.9-ci.151956570 --- eng/Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Versions.props b/eng/Versions.props index 2f2466769430ad..3b7a76d02f735f 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -233,7 +233,7 @@ 8.0.0-rtm.26256.1 - 2.4.18 + 2.5.9-ci.151956570 16.0.5-alpha.1.25311.1 16.0.5-alpha.1.25311.1 From a83db3e0eb2defb6220e15dae2f1a0462fdbf99f Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Fri, 17 Jul 2026 21:05:32 +0000 Subject: [PATCH 4/5] Merged PR 63011: Handle truncation error in ipc_transport_get_default_name. #### AI description (iteration 1) #### PR Classification Bug fix to handle truncation errors in IPC transport default name generation and improve error handling in Unix domain socket address allocation. #### PR Summary This PR fixes error handling when generating default IPC transport names, ensuring truncation errors are properly detected and handled instead of silently proceeding with invalid paths. - `ds-ipc-pal-socket.c`: Added proper error handling with `ep_raise_error_if_nok` macros to validate socket path generation, prevent empty `sun_path` (which would bind to unsupported Linux abstract namespace), and properly cleanup allocated memory on error - `ds-ipc-pal-socket.c` and `ds-rt-coreclr.h`: Modified `ipc_transport_get_default_name` to return `false` when name generation fails (detected by empty string), instead of always returning `true` --- .../vm/eventing/eventpipe/ds-rt-coreclr.h | 5 ++++- src/native/eventpipe/ds-ipc-pal-socket.c | 22 ++++++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) 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/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 From 235d745067657036404f682b4f2658a0fb83e849 Mon Sep 17 00:00:00 2001 From: vseanreesermsft <78103370+vseanreesermsft@users.noreply.github.com> Date: Wed, 12 Aug 2026 09:51:50 -0700 Subject: [PATCH 5/5] Apply suggestion from @ManickaP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marie Píchová <11718369+ManickaP@users.noreply.github.com> --- eng/Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Versions.props b/eng/Versions.props index 2378ebabca5697..6a760347502d9d 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -233,7 +233,7 @@ 8.0.0-rtm.26313.2 - 2.5.9-ci.151956570 + 2.5.10 16.0.5-alpha.1.25311.1 16.0.5-alpha.1.25311.1