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 415b505bb0ea1bd7a3e44257d6dda48cd9991e4f Author: opencode <[email protected]> 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 | 35 ++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/java/org/apache/tomcat/websocket/PerMessageDeflate.java b/java/org/apache/tomcat/websocket/PerMessageDeflate.java index c879cbe167..9b45d20cbf 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 7edecd7fce..69c7927a79 100644 --- a/test/org/apache/tomcat/websocket/TestPerMessageDeflate.java +++ b/test/org/apache/tomcat/websocket/TestPerMessageDeflate.java @@ -799,6 +799,41 @@ public class TestPerMessageDeflate { return sb.toString(); } + /* + * 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: [email protected] For additional commands, e-mail: [email protected]
