You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by re...@apache.org on 2026/10/17 15:50:24 UTC
(tomcat) 05/17: Reject the permessage-deflate compressed bit on continuation frames
This is an automated email from the ASF dual-hosted git repository.
rmaucher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 933e3146c36086a0823e2827fb5ec1d3d51a5683
Author: opencode <re...@apache.org>
AuthorDate: Fri Oct 16 23:37:44 2026 +0200
Reject the permessage-deflate compressed bit on continuation frames
RFC 7692 section 6.1: "An endpoint MUST NOT set the 'Per-Message
Compressed' bit of control frames and non-first fragments of a data
message. An endpoint receiving such a frame MUST Fail the WebSocket
Connection."
PerMessageDeflate.validateRsv() only enforced this for control frames.
For any other opcode it stripped RSV1 and delegated to the next
transformation, so a continuation frame carrying the bit was silently
accepted instead of failing the connection. Route continuation frames
(opcode 0x00, non-first fragments by definition) through the same
reject path so WsFrameBase closes the connection with PROTOCOL_ERROR,
as the RFC requires.
Tomcat's own sender sets RSV1 only on the first compressed frame of a
message (firstCompressedFrameWritten), so this cannot break compressed
fragmented messages between Tomcat endpoints.
Add a regression test to TestPerMessageDeflate covering RSV1 on text,
binary, continuation and control frames, plus RSV clear on continuations.
---
.../apache/tomcat/websocket/PerMessageDeflate.java | 5 ++-
.../tomcat/websocket/TestPerMessageDeflate.java | 36 ++++++++++++++++++++++
2 files changed, 40 insertions(+), 1 deletion(-)
diff --git a/java/org/apache/tomcat/websocket/PerMessageDeflate.java b/java/org/apache/tomcat/websocket/PerMessageDeflate.java
index eaa55f8ad0..1c74e1919a 100644
--- a/java/org/apache/tomcat/websocket/PerMessageDeflate.java
+++ b/java/org/apache/tomcat/websocket/PerMessageDeflate.java
@@ -461,7 +461,10 @@ public class PerMessageDeflate implements Transformation {
@Override
public boolean validateRsv(int rsv, byte opCode) {
- if (Util.isControl(opCode)) {
+ if (Util.isControl(opCode) || Util.isContinuation(opCode)) {
+ // RFC 7692 section 6.1: An endpoint MUST NOT set the "Per-Message Compressed" bit of
+ // control frames and non-first fragments of a data message. An endpoint receiving such
+ // a frame MUST Fail the WebSocket Connection.
if ((rsv & RSV_BITMASK) != 0) {
return false;
} else {
diff --git a/test/org/apache/tomcat/websocket/TestPerMessageDeflate.java b/test/org/apache/tomcat/websocket/TestPerMessageDeflate.java
index 1374652bc6..893905b760 100644
--- a/test/org/apache/tomcat/websocket/TestPerMessageDeflate.java
+++ b/test/org/apache/tomcat/websocket/TestPerMessageDeflate.java
@@ -790,6 +790,42 @@ public class TestPerMessageDeflate {
Assert.assertNull(PerMessageDeflate.build(preferences, true));
}
+
+ /*
+ * RFC 7692 section 6.1: "An endpoint MUST NOT set the 'Per-Message Compressed' bit of control frames and non-first
+ * fragments of a data message. An endpoint receiving such a frame MUST Fail the WebSocket Connection." The RSV1 bit
+ * is only meaningful on the first frame of a compressed data message. Continuation frames (non-first fragments) and
+ * control frames must not carry it.
+ */
+ @Test
+ public void testRsvBitMustNotBeSetOnContinuationFrames() {
+ List<List<Parameter>> preferences = new ArrayList<>();
+ preferences.add(Collections.emptyList());
+
+ // Standalone instance (no next transformation), as the terminal transformation would accept rsv==0.
+ PerMessageDeflate perMessageDeflate = PerMessageDeflate.build(preferences, true);
+
+ // First frame of a compressed data message: RSV1 set is valid.
+ Assert.assertTrue("RSV1 set on a text frame should be accepted",
+ perMessageDeflate.validateRsv(0b100, Constants.OPCODE_TEXT));
+ Assert.assertTrue("RSV1 set on a binary frame should be accepted",
+ perMessageDeflate.validateRsv(0b100, Constants.OPCODE_BINARY));
+
+ // Continuation frame (non-first fragment) with RSV1 set: must fail the connection.
+ Assert.assertFalse("RSV1 set on a continuation frame should be rejected",
+ perMessageDeflate.validateRsv(0b100, Constants.OPCODE_CONTINUATION));
+
+ // Control frame with RSV1 set: must fail the connection (already the case).
+ Assert.assertFalse("RSV1 set on a control frame should be rejected",
+ perMessageDeflate.validateRsv(0b100, Constants.OPCODE_PING));
+
+ // Continuation frame without RSV1: valid (this is the normal case for a
+ // fragmented compressed message).
+ Assert.assertTrue("RSV clear on a continuation frame should be accepted",
+ perMessageDeflate.validateRsv(0, Constants.OPCODE_CONTINUATION));
+ }
+
+
/*
* Minimal implementation to enable other transformations to be tested. It is NOT robust.
*/
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org