From 8c0f919b47b388df610624168888f7d0e83fd7f4 Mon Sep 17 00:00:00 2001 From: Korolev Dmitry Date: Mon, 29 Sep 2025 20:59:31 +0200 Subject: [PATCH 1/2] change WINDOWS_UPDATE received on (half)closed stream to stream-level error instead of connection-level error --- .../Kestrel/Core/src/Internal/Http2/Http2Connection.cs | 8 ++++++-- .../Http2/Http2ConnectionTests.cs | 4 +--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index 689341ddc152..04a9d1d7dff2 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -1158,8 +1158,12 @@ private Task ProcessWindowUpdateFrameAsync() { if (stream.RstStreamReceived) { - // Hard abort, do not allow any more frames on this stream. - throw CreateReceivedFrameStreamAbortedException(stream); + // WINDOW_UPDATE received after we have already processed an inbound RST_STREAM for this stream. + // RFC 7540 (Sections 5.1, 6.9) / RFC 9113 do not explicitly define semantics for WINDOW_UPDATE on a + // stream in the "closed" state due to a reset by client. We surface it as a stream error (STREAM_CLOSED) + // rather than aborting the entire connection to keep behavior deterministic and consistent with other servers. + // https://httpwg.org/specs/rfc7540.html#rfc.section.6.9 + throw new Http2StreamErrorException(_incomingFrame.StreamId, CoreStrings.Http2StreamAborted, Http2ErrorCode.STREAM_CLOSED); } if (!stream.TryUpdateOutputWindow(_incomingFrame.WindowUpdateSizeIncrement)) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs index 41a83e888c20..14760c6abe77 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs @@ -3599,7 +3599,6 @@ public async Task RST_STREAM_IncompleteRequest_AdditionalResetFrame_IgnoreAdditi AssertConnectionNoError(); } - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/53744")] [Fact] public async Task RST_STREAM_IncompleteRequest_AdditionalWindowUpdateFrame_ConnectionAborted() { @@ -3618,8 +3617,7 @@ public async Task RST_STREAM_IncompleteRequest_AdditionalWindowUpdateFrame_Conne await SendRstStreamAsync(1); await SendWindowUpdateAsync(1, 1024); - await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, - Http2ErrorCode.STREAM_CLOSED, CoreStrings.FormatHttp2ErrorStreamAborted(Http2FrameType.WINDOW_UPDATE, 1)); + await WaitForStreamErrorAsync(expectedStreamId: 1, Http2ErrorCode.STREAM_CLOSED, CoreStrings.Http2StreamAborted); tcs.TrySetResult(); // Don't let the response start until after the abort } From 58e51a03d75a5329debff4d0a1c72f5942132d77 Mon Sep 17 00:00:00 2001 From: Korolev Dmitry Date: Mon, 29 Sep 2025 22:00:05 +0200 Subject: [PATCH 2/2] address PR comments --- src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs | 2 +- .../InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index 04a9d1d7dff2..9d3fb8302012 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -1162,7 +1162,7 @@ private Task ProcessWindowUpdateFrameAsync() // RFC 7540 (Sections 5.1, 6.9) / RFC 9113 do not explicitly define semantics for WINDOW_UPDATE on a // stream in the "closed" state due to a reset by client. We surface it as a stream error (STREAM_CLOSED) // rather than aborting the entire connection to keep behavior deterministic and consistent with other servers. - // https://httpwg.org/specs/rfc7540.html#rfc.section.6.9 + // https://github.com/dotnet/aspnetcore/issues/63726 throw new Http2StreamErrorException(_incomingFrame.StreamId, CoreStrings.Http2StreamAborted, Http2ErrorCode.STREAM_CLOSED); } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs index 14760c6abe77..9ab5ceaa4d52 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs @@ -3620,6 +3620,9 @@ public async Task RST_STREAM_IncompleteRequest_AdditionalWindowUpdateFrame_Conne await WaitForStreamErrorAsync(expectedStreamId: 1, Http2ErrorCode.STREAM_CLOSED, CoreStrings.Http2StreamAborted); tcs.TrySetResult(); // Don't let the response start until after the abort + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + AssertConnectionNoError(); } [Fact]