This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/main by this push:
new 2822c1d5dc Treat RST_STREAM during an incomplete HEADERS block as a
connection error (#1124)
2822c1d5dc is described below
commit 2822c1d5dc8e1bcc5c9e1322f3fb386373959c0a
Author: Arturo Bernal <[email protected]>
AuthorDate: Fri Nov 6 17:12:54 2026 +0100
Treat RST_STREAM during an incomplete HEADERS block as a connection error
(#1124)
RFC 9113 section 6.2 (and RFC 7540 section 6.2) require that a HEADERS
frame without the END_HEADERS flag set MUST be followed by a CONTINUATION
frame on the same stream. Any other frame type, including RST_STREAM, MUST
be treated as a connection error of type PROTOCOL_ERROR.
Http2Parser.validateFrame() previously had a NO-OP branch that silently
accepted RST_STREAM frames while a header block was in progress. Remove
that branch so RST_STREAM, like every other non-CONTINUATION frame type,
triggers a ConnectionException with Http2Error.PROTOCOL_ERROR.
Also correct the error code for other interleaved frame types from
COMPRESSION_ERROR to PROTOCOL_ERROR as required by the RFC.
Claude-Session: https://claude.ai/code/session_015BFDJFN9jasV9fx5g4KwAm
---
java/org/apache/coyote/http2/Http2Parser.java | 8 +--
.../apache/coyote/http2/TestHttp2Section_4_3.java | 2 +-
.../apache/coyote/http2/TestHttp2Section_5_5.java | 2 +-
.../apache/coyote/http2/TestHttp2Section_6_2.java | 64 ++++++++++++++++++++++
.../apache/coyote/http2/TestHttp2Section_6_3.java | 2 +-
5 files changed, 70 insertions(+), 8 deletions(-)
diff --git a/java/org/apache/coyote/http2/Http2Parser.java
b/java/org/apache/coyote/http2/Http2Parser.java
index ba4cbd7239..5566314e7e 100644
--- a/java/org/apache/coyote/http2/Http2Parser.java
+++ b/java/org/apache/coyote/http2/Http2Parser.java
@@ -738,13 +738,11 @@ class Http2Parser {
throw new ConnectionException(
sm.getString("http2Parser.headers.wrongStream",
connectionId,
Integer.toString(headersCurrentStream),
Integer.toString(streamId)),
- Http2Error.COMPRESSION_ERROR);
+ Http2Error.PROTOCOL_ERROR);
}
- if (frameType == FrameType.RST) {
- // NO-OP: RST is OK here
- } else if (frameType != FrameType.CONTINUATION) {
+ if (frameType != FrameType.CONTINUATION) {
throw new
ConnectionException(sm.getString("http2Parser.headers.wrongFrameType",
connectionId,
- Integer.toString(headersCurrentStream), frameType),
Http2Error.COMPRESSION_ERROR);
+ Integer.toString(headersCurrentStream), frameType),
Http2Error.PROTOCOL_ERROR);
}
}
diff --git a/test/org/apache/coyote/http2/TestHttp2Section_4_3.java
b/test/org/apache/coyote/http2/TestHttp2Section_4_3.java
index 48798b3cd1..aef53683f3 100644
--- a/test/org/apache/coyote/http2/TestHttp2Section_4_3.java
+++ b/test/org/apache/coyote/http2/TestHttp2Section_4_3.java
@@ -246,6 +246,6 @@ public class TestHttp2Section_4_3 extends Http2TestBase {
sendPing();
- handleGoAwayResponse(3, Http2Error.COMPRESSION_ERROR);
+ handleGoAwayResponse(3, Http2Error.PROTOCOL_ERROR);
}
}
diff --git a/test/org/apache/coyote/http2/TestHttp2Section_5_5.java
b/test/org/apache/coyote/http2/TestHttp2Section_5_5.java
index c202eecb0f..c389c919a9 100644
--- a/test/org/apache/coyote/http2/TestHttp2Section_5_5.java
+++ b/test/org/apache/coyote/http2/TestHttp2Section_5_5.java
@@ -88,6 +88,6 @@ public class TestHttp2Section_5_5 extends Http2TestBase {
os.write(UNKNOWN_FRAME);
os.flush();
- handleGoAwayResponse(3, Http2Error.COMPRESSION_ERROR);
+ handleGoAwayResponse(3, Http2Error.PROTOCOL_ERROR);
}
}
diff --git a/test/org/apache/coyote/http2/TestHttp2Section_6_2.java
b/test/org/apache/coyote/http2/TestHttp2Section_6_2.java
index de9b90415f..85cc497074 100644
--- a/test/org/apache/coyote/http2/TestHttp2Section_6_2.java
+++ b/test/org/apache/coyote/http2/TestHttp2Section_6_2.java
@@ -135,4 +135,68 @@ public class TestHttp2Section_6_2 extends Http2TestBase {
readSimpleGetResponse();
Assert.assertEquals(getSimpleResponseTrace(3), output.getTrace());
}
+
+
+ @Test
+ public void testContinuationFrameOnWrongStream() throws Exception {
+ // RFC 9113, section 6.2: a HEADERS frame without the END_HEADERS flag
set MUST be
+ // followed by a CONTINUATION frame on the same stream. A CONTINUATION
frame on a
+ // different stream MUST be treated as a connection error of type
PROTOCOL_ERROR.
+ http2Connect();
+
+ // Send HEADERS on stream 3 without END_HEADERS
+ byte[] frameHeader = new byte[9];
+ ByteBuffer headersPayload = ByteBuffer.allocate(128);
+ buildSimpleGetRequestPart1(frameHeader, headersPayload, 3);
+ writeFrame(frameHeader, headersPayload);
+
+ // Send CONTINUATION on stream 5 instead of stream 3
+ byte[] frameHeader2 = new byte[9];
+ ByteBuffer headersPayload2 = ByteBuffer.allocate(128);
+ buildSimpleGetRequestPart2(frameHeader2, headersPayload2, 5);
+ writeFrame(frameHeader2, headersPayload2);
+
+ handleGoAwayResponse(3, Http2Error.PROTOCOL_ERROR);
+ }
+
+
+ @Test
+ public void testRstStreamOnDifferentStreamDuringHeaderBlock() throws
Exception {
+ // RFC 9113, section 6.2: a HEADERS frame without the END_HEADERS flag
set MUST be
+ // followed by a CONTINUATION frame on the same stream. A RST_STREAM
frame on a
+ // different stream MUST be treated as a connection error of type
PROTOCOL_ERROR.
+ http2Connect();
+
+ // Send HEADERS on stream 3 without END_HEADERS
+ byte[] frameHeader = new byte[9];
+ ByteBuffer headersPayload = ByteBuffer.allocate(128);
+ buildSimpleGetRequestPart1(frameHeader, headersPayload, 3);
+ writeFrame(frameHeader, headersPayload);
+
+ // Send RST_STREAM for stream 5 instead of CONTINUATION for stream 3
+ sendRst(5, Http2Error.CANCEL.getCode());
+
+ handleGoAwayResponse(3, Http2Error.PROTOCOL_ERROR);
+ }
+
+
+ @Test
+ public void testRstStreamDuringHeaderBlock() throws Exception {
+ // RFC 9113, section 6.2: a HEADERS frame without the END_HEADERS flag
+ // set MUST be followed by a CONTINUATION frame for the same stream.
+ // The receipt of any other frame type, including RST_STREAM, MUST be
+ // treated as a connection error of type PROTOCOL_ERROR.
+ http2Connect();
+
+ // Send HEADERS on stream 3 without the END_HEADERS flag
+ byte[] frameHeader = new byte[9];
+ ByteBuffer headersPayload = ByteBuffer.allocate(128);
+ buildSimpleGetRequestPart1(frameHeader, headersPayload, 3);
+ writeFrame(frameHeader, headersPayload);
+
+ // Send RST_STREAM for stream 3 instead of the required CONTINUATION
+ sendRst(3, Http2Error.CANCEL.getCode());
+
+ handleGoAwayResponse(3, Http2Error.PROTOCOL_ERROR);
+ }
}
diff --git a/test/org/apache/coyote/http2/TestHttp2Section_6_3.java
b/test/org/apache/coyote/http2/TestHttp2Section_6_3.java
index 5432daa27e..62077fbfcb 100644
--- a/test/org/apache/coyote/http2/TestHttp2Section_6_3.java
+++ b/test/org/apache/coyote/http2/TestHttp2Section_6_3.java
@@ -51,7 +51,7 @@ public class TestHttp2Section_6_3 extends Http2TestBase {
sendPriority(5, 3, 15);
- handleGoAwayResponse(3, Http2Error.COMPRESSION_ERROR);
+ handleGoAwayResponse(3, Http2Error.PROTOCOL_ERROR);
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]