You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by tw...@apache.org on 2026/04/07 16:23:17 UTC
(mina-sshd) 05/07: Limit the size of SSH packets after decompression
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 <tw...@apache.org>
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:dev@mina.apache.org">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);
+ }
+ }
+
+}