This is an automated email from the ASF dual-hosted git repository.

twolf pushed a commit to branch dev_3.0
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit 324149d6425ddcf102ca45496c1992214edb0a00
Author: Thomas Wolf <[email protected]>
AuthorDate: Wed Apr 1 19:41:56 2026 +0200

    Limit the size of SSH packets after decompression
    
    A malicious peer might send a packet that passes the initial size
    checks but then inflates to a huge amount of data.
    
    Limit the size of the packet after compression to 256kB. This is fine
    by RFC 4253.[1]
    
    [1] https://datatracker.ietf.org/doc/html/rfc4253#section-6.1
---
 .../sshd/common/compression/CompressionZlib.java   | 27 +++++---
 .../common/compression/CompresssionZlibTest.java   | 74 ++++++++++++++++++++++
 2 files changed, 92 insertions(+), 9 deletions(-)

diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionZlib.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionZlib.java
index 2f5eb222b..12d5751ee 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionZlib.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionZlib.java
@@ -23,6 +23,8 @@ import java.util.zip.DataFormatException;
 import java.util.zip.Deflater;
 import java.util.zip.Inflater;
 
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.util.buffer.Buffer;
 
 /**
@@ -32,11 +34,13 @@ import org.apache.sshd.common.util.buffer.Buffer;
  */
 public class CompressionZlib extends BaseCompression {
 
+    private static final int MAX_UNCOMPRESSED_SIZE = 8 * 
SshConstants.SSH_REQUIRED_PAYLOAD_PACKET_LENGTH_SUPPORT; // 256kB
+
     private static final int BUF_SIZE = 4096;
 
     private byte[] tmpbuf = new byte[BUF_SIZE];
-    private Deflater compresser;
-    private Inflater decompresser;
+    private Deflater compressor;
+    private Inflater decompressor;
 
     /**
      * Create a new instance of a ZLib base compression
@@ -56,26 +60,31 @@ public class CompressionZlib extends BaseCompression {
 
     @Override
     public void init(Type type, int level) {
-        compresser = new Deflater(level);
-        decompresser = new Inflater();
+        compressor = new Deflater(level);
+        decompressor = new Inflater();
     }
 
     @Override
     public void compress(Buffer buffer) throws IOException {
-        compresser.setInput(buffer.array(), buffer.rpos(), buffer.available());
+        compressor.setInput(buffer.array(), buffer.rpos(), buffer.available());
         buffer.wpos(buffer.rpos());
-        for (int len = compresser.deflate(tmpbuf, 0, tmpbuf.length, 
Deflater.SYNC_FLUSH);
+        for (int len = compressor.deflate(tmpbuf, 0, tmpbuf.length, 
Deflater.SYNC_FLUSH);
              len > 0;
-             len = compresser.deflate(tmpbuf, 0, tmpbuf.length, 
Deflater.SYNC_FLUSH)) {
+             len = compressor.deflate(tmpbuf, 0, tmpbuf.length, 
Deflater.SYNC_FLUSH)) {
             buffer.putRawBytes(tmpbuf, 0, len);
         }
     }
 
     @Override
     public void uncompress(Buffer from, Buffer to) throws IOException {
-        decompresser.setInput(from.array(), from.rpos(), from.available());
+        decompressor.setInput(from.array(), from.rpos(), from.available());
+        int start = to.wpos();
         try {
-            for (int len = decompresser.inflate(tmpbuf); len > 0; len = 
decompresser.inflate(tmpbuf)) {
+            for (int len = decompressor.inflate(tmpbuf); len > 0; len = 
decompressor.inflate(tmpbuf)) {
+                if (to.wpos() + len - start > MAX_UNCOMPRESSED_SIZE) {
+                    throw new 
SshException(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR,
+                            "Compressed SSH packet inflated to more than 
256kB");
+                }
                 to.putRawBytes(tmpbuf, 0, len);
             }
         } catch (DataFormatException e) {
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/compression/CompresssionZlibTest.java
 
b/sshd-common/src/test/java/org/apache/sshd/common/compression/CompresssionZlibTest.java
new file mode 100644
index 000000000..a563b3e37
--- /dev/null
+++ 
b/sshd-common/src/test/java/org/apache/sshd/common/compression/CompresssionZlibTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.sshd.common.compression;
+
+import java.io.IOException;
+
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.junit.jupiter.api.MethodOrderer.MethodName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+@TestMethodOrder(MethodName.class)
+@Tag("NoIoTestCase")
+class CompresssionZlibTest extends JUnitTestSupport {
+
+    CompresssionZlibTest() {
+        super();
+    }
+
+    @ParameterizedTest
+    @ValueSource(ints = { 1, 32, 255, 256, 257, 258, 259, 260, 261, 300 })
+    void maxSize(int size) throws IOException {
+        boolean expectSuccess = size <= 256;
+        byte[] data = new byte[size * 1024];
+        Compression comp = BuiltinCompressions.zlib.create();
+        comp.init(Compression.Type.Deflater, 9);
+        Buffer buf = new ByteArrayBuffer();
+        buf.putRawBytes(data);
+        assertEquals(data.length, buf.available());
+        comp.compress(buf);
+        assertTrue(buf.available() < data.length);
+        assertTrue(buf.available() <= 256 * 1024);
+
+        Compression decompress = BuiltinCompressions.zlib.create();
+        decompress.init(Compression.Type.Inflater, 9);
+        Buffer dec = new ByteArrayBuffer();
+        if (expectSuccess) {
+            decompress.uncompress(buf, dec);
+            assertEquals(data.length, dec.available());
+            assertArrayEquals(data, dec.getCompactData());
+        } else {
+            assertThrows(SshException.class, () -> {
+                decompress.uncompress(buf, dec);
+            });
+            assertTrue(dec.available() <= 256 * 1024);
+        }
+    }
+
+}

Reply via email to