This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 10.1.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 0deca6b6914e60ca81d6e928efeb842226345427 Author: opencode <[email protected]> AuthorDate: Fri Oct 9 14:09:02 2026 +0200 Validate FragmentationInterceptor maxSize and expire configuration setMaxSize(0) made frag() divide by zero, throwing ArithmeticException on every message above the threshold, and a negative max size produced a negative fragment count and a NegativeArraySizeException. A non-positive expire made FragCollection.expired() true immediately, so the heartbeat removed every fragment collection and fragmented messages were silently never reassembled. Reject values below one in both setters, mirroring the validation style of EncryptInterceptor.setReplayWindowTime. As a related guard, fail a send with a ChannelException when the configured max size would require more fragments than the receiver accepts via MAX_FRAGMENTS, instead of sending fragments that are guaranteed to be discarded. --- .../interceptors/FragmentationInterceptor.java | 12 +++++ .../group/interceptors/LocalStrings.properties | 3 ++ .../interceptors/TestFragmentationInterceptor.java | 61 ++++++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/java/org/apache/catalina/tribes/group/interceptors/FragmentationInterceptor.java b/java/org/apache/catalina/tribes/group/interceptors/FragmentationInterceptor.java index ec550c411e..509942496b 100644 --- a/java/org/apache/catalina/tribes/group/interceptors/FragmentationInterceptor.java +++ b/java/org/apache/catalina/tribes/group/interceptors/FragmentationInterceptor.java @@ -171,6 +171,12 @@ public class FragmentationInterceptor extends ChannelInterceptorBase implements int size = msg.getMessage().getLength(); int count = ((size / maxSize) + (size % maxSize == 0 ? 0 : 1)); + if (count > MAX_FRAGMENTS) { + // The receiver rejects fragment counts above MAX_FRAGMENTS, so fail here + // rather than sending fragments that are guaranteed to be discarded. + throw new ChannelException(sm.getString("fragmentationInterceptor.fragments.exceed-max", + Integer.toString(size), Integer.toString(count), Integer.toString(MAX_FRAGMENTS))); + } ChannelMessage[] messages = new ChannelMessage[count]; int remaining = size; for (int i = 0; i < count; i++) { @@ -229,11 +235,17 @@ public class FragmentationInterceptor extends ChannelInterceptorBase implements @Override public void setMaxSize(int maxSize) { + if (maxSize < 1) { + throw new IllegalArgumentException(sm.getString("fragmentationInterceptor.maxSize.tooSmall")); + } this.maxSize = maxSize; } @Override public void setExpire(long expire) { + if (expire < 1) { + throw new IllegalArgumentException(sm.getString("fragmentationInterceptor.expire.tooSmall")); + } this.expire = expire; } diff --git a/java/org/apache/catalina/tribes/group/interceptors/LocalStrings.properties b/java/org/apache/catalina/tribes/group/interceptors/LocalStrings.properties index 49a2a52fa6..df34613fb0 100644 --- a/java/org/apache/catalina/tribes/group/interceptors/LocalStrings.properties +++ b/java/org/apache/catalina/tribes/group/interceptors/LocalStrings.properties @@ -32,13 +32,16 @@ encryptInterceptor.replayWindowMessageCount.tooSmall=Replay window message count encryptInterceptor.replayWindowTime.tooSmall=Replay window time must be at least 1 millisecond encryptInterceptor.tcpFailureDetector.ordering=EncryptInterceptor must be upstream of TcpFailureDetector. Please re-order EncryptInterceptor to be listed before TcpFailureDetector in your channel interceptor pipeline. +fragmentationInterceptor.expire.tooSmall=Expire must be at least 1 millisecond fragmentationInterceptor.fragment.discarded=Received fragment failed validation. The fragment is discarded. fragmentationInterceptor.fragment.too-short=Received fragment with too little data, length [{0}]. +fragmentationInterceptor.fragments.exceed-max=Unable to fragment a message of [{0}] bytes into [{1}] fragments, the maximum number of fragments is [{2}]. fragmentationInterceptor.fragments.missing=Fragments are missing. fragmentationInterceptor.fragments.too-large=The combined length [{0}] of the received fragments exceeds the maximum supported message size. fragmentationInterceptor.heartbeat.failed=Unable to perform heartbeat clean up in the frag interceptor fragmentationInterceptor.invalid.frag.count=Received invalid fragment count [{0}]. The fragment is discarded. fragmentationInterceptor.invalid.frag.nr=Received fragment number [{0}] for a message with [{1}] fragments. The fragment is discarded. +fragmentationInterceptor.maxSize.tooSmall=Max size must be at least 1 byte gzipInterceptor.compress.failed=Unable to compress byte contents gzipInterceptor.decompress.failed=Unable to decompress byte contents diff --git a/test/org/apache/catalina/tribes/group/interceptors/TestFragmentationInterceptor.java b/test/org/apache/catalina/tribes/group/interceptors/TestFragmentationInterceptor.java index b9f70730f8..6fa862d429 100644 --- a/test/org/apache/catalina/tribes/group/interceptors/TestFragmentationInterceptor.java +++ b/test/org/apache/catalina/tribes/group/interceptors/TestFragmentationInterceptor.java @@ -82,6 +82,67 @@ public class TestFragmentationInterceptor { return Arrays.copyOfRange(msg.getMessage().getBytesDirect(), 0, len); } + @Test + public void testSetMaxSizeRejectsInvalidValues() { + FragmentationInterceptor interceptor = new FragmentationInterceptor(); + + try { + interceptor.setMaxSize(0); + Assert.fail("A max size of zero would make fragmentation divide by zero"); + } catch (IllegalArgumentException expected) { + // Expected + } + + try { + interceptor.setMaxSize(-1); + Assert.fail("A negative max size would make fragmentation allocate a negative sized array"); + } catch (IllegalArgumentException expected) { + // Expected + } + + interceptor.setMaxSize(1024); + Assert.assertEquals(1024, interceptor.getMaxSize()); + } + + @Test + public void testSetExpireRejectsInvalidValues() { + FragmentationInterceptor interceptor = new FragmentationInterceptor(); + + try { + interceptor.setExpire(0); + Assert.fail("A non-positive expire would discard fragment collections before reassembly can complete"); + } catch (IllegalArgumentException expected) { + // Expected + } + + try { + interceptor.setExpire(-1); + Assert.fail("A negative expire would discard fragment collections before reassembly can complete"); + } catch (IllegalArgumentException expected) { + // Expected + } + + interceptor.setExpire(60000); + Assert.assertEquals(60000, interceptor.getExpire()); + } + + @Test + public void testFragExceedingMaxFragmentsFails() throws Exception { + Member destination = new MemberImpl("localhost", 4000, -1); + FragmentationInterceptor sender = new FragmentationInterceptor(); + sender.setMaxSize(1); + sender.setChannel(new GroupChannel()); + sender.setNext(new CapturingSender()); + + byte[] payload = new byte[FragmentationInterceptor.MAX_FRAGMENTS + 1]; + try { + sender.sendMessage(new Member[] { destination }, createMessage(payload, destination), null); + Assert.fail("Fragmenting into more than MAX_FRAGMENTS fragments should fail"); + } catch (ChannelException expected) { + // Expected + } + } + @Test public void testFragmentRoundTrip() throws Exception { Member destination = new MemberImpl("localhost", 4000, -1); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
