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);  
+    }
 }

Reply via email to