This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 9.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit dd447ac1ab2edcbe017cd9413895a89effcdfc1a Author: opencode <[email protected]> AuthorDate: Tue Oct 13 16:59:23 2026 +0200 Fix CLOSED_RX unexpected frames handling RFC 9113 5.1 closed: An endpoint that sends a frame with the END_STREAM flag set or an RST_STREAM frame might receive a WINDOW_UPDATE or RST_STREAM frame from its peer in the time before the peer receives and processes the frame that closes the stream. An endpoint MUST NOT consider receipt of such a frame to be an error. The state reached when the final frame of the stream is received (CLOSED_RX) permitted only PRIORITY frames, while the symmetric state reached when the final frame is sent (CLOSED_TX) already permitted the in flight WINDOW_UPDATE and RST_STREAM grace frames. A stream closed by sending then receiving the end of stream therefore saw these legitimate in flight frames trigger a connection error of type STREAM_CLOSED, a GOAWAY and the loss of every multiplexed stream on an otherwise healthy connection. Permit the grace frames in CLOSED_RX, matching CLOSED_TX. Streams that have been replaced by the low memory representation already ignore these frames once the state check passes, and for a live stream the handling is identical to the currently tolerated CLOSED_TX case. --- .../apache/coyote/http2/StreamStateMachine.java | 4 +- .../coyote/http2/TestStreamStateMachine.java | 68 ++++++++++++++++++++++ webapps/docs/changelog.xml | 8 +++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/java/org/apache/coyote/http2/StreamStateMachine.java b/java/org/apache/coyote/http2/StreamStateMachine.java index 92a49810c3..f3f0f02d51 100644 --- a/java/org/apache/coyote/http2/StreamStateMachine.java +++ b/java/org/apache/coyote/http2/StreamStateMachine.java @@ -195,7 +195,9 @@ class StreamStateMachine { FrameType.RST, FrameType.WINDOW_UPDATE), CLOSED_RX (false, false, false, true, - Http2Error.STREAM_CLOSED, FrameType.PRIORITY), + Http2Error.STREAM_CLOSED, FrameType.PRIORITY, + FrameType.RST, + FrameType.WINDOW_UPDATE), CLOSED_TX (false, false, false, true, Http2Error.STREAM_CLOSED, FrameType.PRIORITY, FrameType.RST, diff --git a/test/org/apache/coyote/http2/TestStreamStateMachine.java b/test/org/apache/coyote/http2/TestStreamStateMachine.java new file mode 100644 index 0000000000..392b46b275 --- /dev/null +++ b/test/org/apache/coyote/http2/TestStreamStateMachine.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.coyote.http2; + +import org.junit.Assert; +import org.junit.Test; + +public class TestStreamStateMachine { + + private static final String CONNECTION_ID = "-1"; + private static final String STREAM_ID = "3"; + + /* + * RFC 9113 section 5.1: an endpoint that sends the frame that closed the stream might receive a WINDOW_UPDATE or + * RST_STREAM frame from its peer in the time before the peer receives and processes that closing frame. Receipt of + * such frames must not be considered an error. These are the same frames permitted for the closed state reached + * when the final frame is sent rather than received. + */ + @Test + public void closedRxAcceptsGraceFrames() throws Exception { + StreamStateMachine state = new StreamStateMachine(CONNECTION_ID, STREAM_ID); + + // Close the stream by sending then receiving the end of stream. + state.receivedStartOfHeaders(); + state.sentEndOfStream(); + state.receivedEndOfStream(); + + state.checkFrameType(FrameType.PRIORITY); + state.checkFrameType(FrameType.RST); + state.checkFrameType(FrameType.WINDOW_UPDATE); + } + + + /* + * RFC 9113 section 5.1: receipt of any other frame type on a closed stream may be treated as a connection error of + * type STREAM_CLOSED. + */ + @Test + public void closedRxRejectsOtherFrames() throws Exception { + StreamStateMachine state = new StreamStateMachine(CONNECTION_ID, STREAM_ID); + + // Close the stream by sending then receiving the end of stream. + state.receivedStartOfHeaders(); + state.sentEndOfStream(); + state.receivedEndOfStream(); + + try { + state.checkFrameType(FrameType.DATA); + Assert.fail("Expected a connection error for a DATA frame received in the closed state"); + } catch (ConnectionException ce) { + Assert.assertEquals(Http2Error.STREAM_CLOSED, ce.getError()); + } + } +} diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index c11a903ad9..4115eb3ba9 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -303,6 +303,14 @@ allocation remain in the back log and are considered again when the next window increment arrives. (remm) </fix> + <fix> + Permit the <code>WINDOW_UPDATE</code> and <code>RST_STREAM</code> + frames that may legitimately be in flight when an HTTP/2 stream is + closed by the receipt of an end of stream frame, rather than treating + them as a connection error of type <code>STREAM_CLOSED</code> that + closes the entire connection. These frames are already permitted when + the stream is closed in the other direction. (remm) + </fix> </changelog> </subsection> <subsection name="Web applications"> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
