This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch 2.2.X
in repository https://gitbox.apache.org/repos/asf/mina.git
The following commit(s) were added to refs/heads/2.2.X by this push:
new accfb472e Added a size limit for the inflated buffer
accfb472e is described below
commit accfb472e610c9075c096adc3ee17e4a41fc64c2
Author: Emmanuel Lécharny <[email protected]>
AuthorDate: Tue May 19 09:39:13 2026 +0200
Added a size limit for the inflated buffer
---
.../mina/filter/compression/CompressionFilter.java | 45 ++++++--
.../org/apache/mina/filter/compression/Zlib.java | 120 +++++++++++++++------
.../apache/mina/filter/compression/ZlibTest.java | 60 +++++++++++
3 files changed, 187 insertions(+), 38 deletions(-)
diff --git
a/mina-filter-compression/src/main/java/org/apache/mina/filter/compression/CompressionFilter.java
b/mina-filter-compression/src/main/java/org/apache/mina/filter/compression/CompressionFilter.java
index b978bdc68..55b21337d 100644
---
a/mina-filter-compression/src/main/java/org/apache/mina/filter/compression/CompressionFilter.java
+++
b/mina-filter-compression/src/main/java/org/apache/mina/filter/compression/CompressionFilter.java
@@ -52,6 +52,11 @@ import org.apache.mina.core.write.WriteRequest;
* <p>
* It goes without saying that the other end of this stream should also have a
* compatible compressor/decompressor using the same algorithm.
+ * <p>
+ * Note: a inflater limit has been added to protect the application from ZBomb
+ * (a compressed buffer that when inflated will create a giant buffer).
+ * It can be set using the CompressionFilter constructor, passing a forth
argument
+ * with the expected limit.
*
* @author <a href="http://mina.apache.org">Apache MINA Project</a>
*/
@@ -91,20 +96,24 @@ public class CompressionFilter extends IoFilterAdapter {
/**
* A flag that allows you to disable compression once.
*/
- public static final AttributeKey DISABLE_COMPRESSION_ONCE = new
AttributeKey(CompressionFilter.class, "disableOnce");
+ public static final AttributeKey DISABLE_COMPRESSION_ONCE =
+ new AttributeKey(CompressionFilter.class, "disableOnce");
private boolean compressInbound = true;
private boolean compressOutbound = true;
private int compressionLevel;
+
+ /** The maximum decompressed size, to avoid an OOM. Default to 1Mb */
+ private int maxDecompressedSize;
/**
* Creates a new instance which compresses outboud data and decompresses
* inbound data with default compression level.
*/
public CompressionFilter() {
- this(true, true, COMPRESSION_DEFAULT);
+ this(true, true, COMPRESSION_DEFAULT, Zlib.MAX_DECOMPRESSED_SIZE);
}
/**
@@ -118,11 +127,29 @@ public class CompressionFilter extends IoFilterAdapter {
* {@link #COMPRESSION_NONE}.
*/
public CompressionFilter(final int compressionLevel) {
- this(true, true, compressionLevel);
+ this(true, true, compressionLevel, Zlib.MAX_DECOMPRESSED_SIZE);
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * @param compressInbound <code>true</code> if data read is to be
decompressed
+ * @param compressOutbound <code>true</code> if data written is to be
compressed
+ * @param compressionLevel the level of compression to be used. Must
+ * be one of {@link #COMPRESSION_DEFAULT},
+ * {@link #COMPRESSION_MAX},
+ * {@link #COMPRESSION_MIN}, and
+ * {@link #COMPRESSION_NONE}.
+ */
+ public CompressionFilter(final boolean compressInbound, final boolean
compressOutbound,
+ final int compressionLevel) {
+ this(true, true, compressionLevel, Zlib.MAX_DECOMPRESSED_SIZE);
}
/**
* Creates a new instance.
+ * <p>
+ * Use thgis constructor if you want to set a limit to the inflated buffer
size.
*
* @param compressInbound <code>true</code> if data read is to be
decompressed
* @param compressOutbound <code>true</code> if data written is to be
compressed
@@ -131,11 +158,14 @@ public class CompressionFilter extends IoFilterAdapter {
* {@link #COMPRESSION_MAX},
* {@link #COMPRESSION_MIN}, and
* {@link #COMPRESSION_NONE}.
+ * @param maxDecompressedSize The maximum size for a buffer when inflating
some data
*/
- public CompressionFilter(final boolean compressInbound, final boolean
compressOutbound, final int compressionLevel) {
+ public CompressionFilter(final boolean compressInbound, final boolean
compressOutbound,
+ final int compressionLevel, final int maxDecompressedSize) {
this.compressionLevel = compressionLevel;
this.compressInbound = compressInbound;
this.compressOutbound = compressOutbound;
+ this.maxDecompressedSize = maxDecompressedSize;
}
/**
@@ -170,7 +200,10 @@ public class CompressionFilter extends IoFilterAdapter {
}
/*
- * @see
org.apache.mina.core.IoFilter#filterWrite(org.apache.mina.core.IoFilter.NextFilter,
org.apache.mina.core.IoSession, org.apache.mina.core.IoFilter.WriteRequest)
+ * @see org.apache.mina.core.IoFilter#filterWrite(
+ * org.apache.mina.core.IoFilter.NextFilter,
+ * org.apache.mina.core.IoSession,
+ * org.apache.mina.core.IoFilter.WriteRequest)
*/
protected Object doFilterWrite(NextFilter nextFilter, IoSession session,
WriteRequest writeRequest)
throws IOException {
@@ -207,7 +240,7 @@ public class CompressionFilter extends IoFilterAdapter {
}
Zlib deflater = new Zlib(compressionLevel, Zlib.MODE_DEFLATER);
- Zlib inflater = new Zlib(compressionLevel, Zlib.MODE_INFLATER);
+ Zlib inflater = new Zlib(compressionLevel, Zlib.MODE_INFLATER,
maxDecompressedSize);
IoSession session = parent.getSession();
diff --git
a/mina-filter-compression/src/main/java/org/apache/mina/filter/compression/Zlib.java
b/mina-filter-compression/src/main/java/org/apache/mina/filter/compression/Zlib.java
index 20cbf3187..44f315b76 100644
---
a/mina-filter-compression/src/main/java/org/apache/mina/filter/compression/Zlib.java
+++
b/mina-filter-compression/src/main/java/org/apache/mina/filter/compression/Zlib.java
@@ -54,6 +54,12 @@ class Zlib {
/** The requested compression level */
private int compressionLevel;
+
+ /** The maximum size of an inflated buffer. Default to 1Mb */
+ /* Package protected */
+ static final int MAX_DECOMPRESSED_SIZE = Integer.MAX_VALUE;
+
+ private int maxDecompressedSize = MAX_DECOMPRESSED_SIZE;
/** The inner stream used to inflate or deflate the data */
private ZStream zStream = null;
@@ -73,31 +79,75 @@ class Zlib {
*/
public Zlib(int compressionLevel, int mode) {
switch (compressionLevel) {
- case COMPRESSION_MAX:
- case COMPRESSION_MIN:
- case COMPRESSION_NONE:
- case COMPRESSION_DEFAULT:
- this.compressionLevel = compressionLevel;
- break;
- default:
- throw new IllegalArgumentException("invalid compression level
specified");
+ case COMPRESSION_MAX:
+ case COMPRESSION_MIN:
+ case COMPRESSION_NONE:
+ case COMPRESSION_DEFAULT:
+ this.compressionLevel = compressionLevel;
+ break;
+ default:
+ throw new IllegalArgumentException("invalid compression level
specified");
+ }
+
+ // create a new instance of ZStream. This will be done only once.
+ zStream = new ZStream();
+
+ switch (mode) {
+ case MODE_DEFLATER:
+ zStream.deflateInit(this.compressionLevel);
+ break;
+ case MODE_INFLATER:
+ zStream.inflateInit();
+ break;
+ default:
+ throw new IllegalArgumentException("invalid mode specified");
+ }
+
+ this.mode = mode;
+ }
+
+
+ /**
+ * Creates an instance of the ZLib class.
+ *
+ * @param compressionLevel the level of compression that should be used.
One of
+ * <code>COMPRESSION_MAX</code>, <code>COMPRESSION_MIN</code>,
+ * <code>COMPRESSION_NONE</code> or <code>COMPRESSION_DEFAULT</code>
+ * @param mode the mode in which the instance will operate. Can be either
+ * of <code>MODE_DEFLATER</code> or <code>MODE_INFLATER</code>
+ * @param maxDecompressedSize The maximum inflation size for a buffer.
Default to 1MB
+ * @throws IllegalArgumentException if the mode is incorrect
+ */
+ public Zlib(int compressionLevel, int mode, int maxDecompressedSize) {
+ switch (compressionLevel) {
+ case COMPRESSION_MAX:
+ case COMPRESSION_MIN:
+ case COMPRESSION_NONE:
+ case COMPRESSION_DEFAULT:
+ this.compressionLevel = compressionLevel;
+ break;
+ default:
+ throw new IllegalArgumentException("invalid compression level
specified");
}
// create a new instance of ZStream. This will be done only once.
zStream = new ZStream();
switch (mode) {
- case MODE_DEFLATER:
- zStream.deflateInit(this.compressionLevel);
- break;
- case MODE_INFLATER:
- zStream.inflateInit();
- break;
- default:
- throw new IllegalArgumentException("invalid mode specified");
+ case MODE_DEFLATER:
+ zStream.deflateInit(this.compressionLevel);
+ break;
+ case MODE_INFLATER:
+ this.maxDecompressedSize = maxDecompressedSize;
+ zStream.inflateInit();
+ break;
+ default:
+ throw new IllegalArgumentException("invalid mode specified");
}
+
this.mode = mode;
}
+
/**
* Uncompress the given buffer, returning it in a new buffer.
@@ -135,22 +185,28 @@ class Zlib {
do {
retval = zStream.inflate(JZlib.Z_SYNC_FLUSH);
switch (retval) {
- case JZlib.Z_OK:
- // completed decompression, lets copy data and get out
- case JZlib.Z_BUF_ERROR:
- // need more space for output. store current output and
get more
- outBuffer.put(outBytes, 0, zStream.next_out_index);
- zStream.next_out_index = 0;
- zStream.avail_out = outBytes.length;
- break;
- default:
- // unknown error
- outBuffer = null;
- if (zStream.msg == null) {
- throw new IOException("Unknown error. Error code : " +
retval);
- } else {
- throw new IOException("Unknown error. Error code : " +
retval + " and message : " + zStream.msg);
- }
+ case JZlib.Z_OK:
+ // completed decompression, lets copy data and get out
+ case JZlib.Z_BUF_ERROR:
+ // Try to avoid exhausting the JVM memory by
controling the resulting buffer
+ // size after inflation
+ if (outBuffer.position() + zStream.next_out_index >
maxDecompressedSize) {
+ throw new IOException("decompressed size exceeds
max " + maxDecompressedSize);
+ }
+
+ // need more space for output. store current output
and get more
+ outBuffer.put(outBytes, 0, zStream.next_out_index);
+ zStream.next_out_index = 0;
+ zStream.avail_out = outBytes.length;
+ break;
+ default:
+ // unknown error
+ outBuffer = null;
+ if (zStream.msg == null) {
+ throw new IOException("Unknown error. Error code :
" + retval);
+ } else {
+ throw new IOException("Unknown error. Error code :
" + retval + " and message : " + zStream.msg);
+ }
}
} while (zStream.avail_in > 0);
diff --git
a/mina-filter-compression/src/test/java/org/apache/mina/filter/compression/ZlibTest.java
b/mina-filter-compression/src/test/java/org/apache/mina/filter/compression/ZlibTest.java
index a3c3da493..af053e1b0 100644
---
a/mina-filter-compression/src/test/java/org/apache/mina/filter/compression/ZlibTest.java
+++
b/mina-filter-compression/src/test/java/org/apache/mina/filter/compression/ZlibTest.java
@@ -126,4 +126,64 @@ public class ZlibTest {
String strOutput =
byteUncompressed.getString(Charset.forName("UTF8").newDecoder());
assertTrue(strOutput.equals(strInput));
}
+
+
+ /**
+ * Test the inflater with no limit
+ * We create buffers of various sizes:
+ * <ul>
+ * <li>A 1MB buffer that once compressed should inflate properly
+ * <li>A 10MB buffer that once compressed should inflate properly
+ * <li>
+ * </ul>
+ * @throws Exception
+ */
+ @Test
+ public void testZBombDataNoLimit() throws Exception {
+ // Create an inflater with no size limit
+ Zlib inflaterNoLimit = new Zlib(Zlib.COMPRESSION_MAX,
Zlib.MODE_INFLATER);
+
+ // Try a 10MB buffer bomb. Should succeed
+ byte[] uncompressed = new byte[1_024*1_024*10];
+
+ IoBuffer byteInput = IoBuffer.wrap(uncompressed);
+ IoBuffer byteCompressed = deflater.deflate(byteInput);
+
+ // Should be fine
+ inflaterNoLimit.inflate(byteCompressed);
+ }
+
+
+ /**
+ * Test the inflater default limit.
+ * We create buffers of various sizes:
+ * <ul>
+ * <li>A 1MB Buffer that once compressed should inflate properly
+ * <li>A 1MB+1byte buffer that once compressed should generate an
exception when inflated
+ * <li>
+ * </ul>
+ * @throws Exception
+ */
+ @Test(expected=IOException.class)
+ public void testZBombData() throws Exception {
+ // Create an inflater with a 1Mb size limit
+ Zlib inflaterWithLimit = new Zlib(Zlib.COMPRESSION_MAX,
Zlib.MODE_INFLATER, 1_024*1_024);
+
+ // Try a 1MB buffer bomb. Should succeed
+ byte[] uncompressed = new byte[1_024*1_024];
+
+ IoBuffer byteInput = IoBuffer.wrap(uncompressed);
+ IoBuffer byteCompressed = deflater.deflate(byteInput);
+
+ // Should be fine
+ inflaterWithLimit.inflate(byteCompressed);
+
+ // Now try with a 1Mb +1 byte buffer
+ uncompressed = new byte[1_024*1_024+1];
+ byteInput = IoBuffer.wrap(uncompressed);
+ byteCompressed = deflater.deflate(byteInput);
+
+ // Should now fail and throw a IoException
+ inflaterWithLimit.inflate(byteCompressed);
+ }
}