From dbae1481593bcee5eb70b151b18593a40074cbaf 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:04:34 +0000 Subject: [PATCH 1/5] Merged PR 62372: [release/9.0] Fix WebSocked inflater handling of BFinal Fix handling of BFinal in WebSocket deflate. [MSRC] [121599] ---- #### AI description (iteration 1) #### PR Classification Bug fix to properly handle WebSocket compressed messages that contain a DEFLATE final block (BFINAL=1 bit set). #### PR Summary Fixes a bug where WebSocket inflater would hang indefinitely when receiving compressed messages terminated with a DEFLATE BFINAL=1 block, which violates the permessage-deflate specification. The inflater now detects and rejects such malformed messages. - `WebSocketInflater.cs`: Added detection logic to check if the DEFLATE stream ended (BFINAL=1) while compressed bytes remain unconsumed, throwing a `WebSocketException` to prevent infinite loops - `WebSocketInflater.cs`: Modified `Inflate` method signature to return `streamEnded` status via an out parameter to track when zlib encounters a final block - `WebSocketDeflateTests.cs`: Added comprehensive test cases covering BFINAL-terminated frames, including both complete and fragmented messages, to verify the fix - `Strings.resx`: Added new error message resource `net_WebSockets_DataAfterBFinal` for the exception thrown when invalid BFINAL termination is detected --- .../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 a6033ed9cf8313..1a0c5dc1690cf5 100644 --- a/src/libraries/System.Net.WebSockets/src/Resources/Strings.resx +++ b/src/libraries/System.Net.WebSockets/src/Resources/Strings.resx @@ -165,6 +165,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 4a25fcb03d1048..5c1c797df610d5 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 bool IsFinished(ZLibStreamHandle stream, out byte? remainingByte) // 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 = 0; - if (Inflate(stream, new Span(ref b), FlushCode.SyncFlush) == 0) + if (Inflate(stream, new Span(ref b), FlushCode.SyncFlush, out _) == 0) { remainingByte = null; return true; @@ -225,7 +237,7 @@ private static bool IsFinished(ZLibStreamHandle stream, out byte? remainingByte) 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 1e80e82bdde01a..065f47dc43388b 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 @@ -935,7 +935,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 c458318a4386122b78e9291c1bacc11c71516d65 Mon Sep 17 00:00:00 2001 From: Irem Yuksel Date: Mon, 13 Jul 2026 19:10:44 +0000 Subject: [PATCH 2/5] Merged PR 62799: [release/9.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 reject invalid content length values in HttpListenerRequest.Managed implementation. #### PR Summary This PR fixes the managed HttpListener implementation to strictly reject invalid Content-Length header values (like values exceeding long.MaxValue) instead of treating them as 0, aligning with the Windows parser behavior. - `HttpListenerRequest.Managed.cs`: Changed Content-Length parsing to use strict `long.TryParse` with `NumberStyles.None` to reject values outside valid range instead of converting oversized values to 0 - `InvalidClientRequestTests.cs`: Added new test cases to verify that oversized Content-Length values (long.MaxValue+1 and ulong.MaxValue) result in "Bad Request" errors - `HttpListenerRequestTests.cs`: Removed test cases that previously expected oversized Content-Length values to be accepted and treated 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 51c3ebfa01fb1b9b65e48f7fb98093db39d61810 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:07:49 +0000 Subject: [PATCH 3/5] Merged PR 62895: [release/9.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 native library version for QUIC protocol support. #### PR Summary This pull request updates the MsQuic Schannel dependency from version 2.4.18 to 2.5.9-ci.151956570 in the release/9.0 branch. - `eng/Versions.props`: Updated `MicrosoftNativeQuicMsQuicSchannelVersion` package version 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 08afe01a5f2448..20020765e5f3da 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -228,7 +228,7 @@ 9.0.0-rtm.26278.1 - 2.4.18 + 2.5.9-ci.151956570 19.1.0-alpha.1.26329.2 19.1.0-alpha.1.26329.2 From 8381bdb01fe4a26e1f61370e779c78bb73ecc95b Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Fri, 17 Jul 2026 21:05:36 +0000 Subject: [PATCH 4/5] Merged PR 63010: Handle truncation error in ipc_transport_get_default_name. #### AI description (iteration 1) #### PR Classification Bug fix to properly handle truncation errors when generating default IPC transport names. #### PR Summary This PR fixes error handling in the IPC transport name generation code to prevent silent failures when socket paths are truncated or invalid. The changes ensure that failures in `PAL_GetTransportName` are properly detected and propagated, and that empty socket paths (which would incorrectly bind to Linux abstract namespace) are rejected. - `ds-ipc-pal-socket.c`: Added proper error handling with `ep_raise_error_if_nok` macros for socket name generation, validation to reject empty `sun_path`, and cleanup logic to free allocated memory on error - `ds-ipc-pal-socket.c` and `ds-rt-coreclr.h`: Modified `ipc_transport_get_default_name` and `ds_rt_transport_get_default_name` to return `false` when name generation fails (detected by empty string) ---- #### AI description (iteration 1) #### PR Classification Bug fix to properly handle truncation errors when generating default IPC transport names in EventPipe socket operations. #### PR Summary This PR adds proper error handling for socket path truncation in the IPC transport layer, ensuring that failed name generation is detected and handled gracefully rather than silently creating invalid socket paths. - `/src/native/eventpipe/ds-ipc-pal-socket.c`: Added error checking for `ipc_transport_get_default_name` return value, validation that `sun_path` is not empty to prevent binding to Linux abstract namespace, and proper error cleanup with memory deallocation - `/src/native/eventpipe/ds-ipc-pal-socket.c` and `/src/coreclr/vm/eventing/eventpipe/ds-rt-coreclr.h`: Changed `ipc_transport_get_default_name` and `ds_rt_transport_get_default_name` to return `false` when name generation fails (indicated by empty string) --- .../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 fb6c0c3feeda09..ca414d47d1c81c 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 5ae2990fbe5308cb3a7f49371b30da062bf434bc Mon Sep 17 00:00:00 2001 From: vseanreesermsft <78103370+vseanreesermsft@users.noreply.github.com> Date: Wed, 12 Aug 2026 09:51:21 -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 126d39cbcf0281..ca5728c1a660b6 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -228,7 +228,7 @@ 9.0.0-rtm.26358.1 - 2.5.9-ci.151956570 + 2.5.10 19.1.0-alpha.1.26329.2 19.1.0-alpha.1.26329.2