CVE-2023-34455: Allocation of Resources Without Limits or Throttling (CWE-770)
value: snappy-java is a fast compressor/decompressor for Java. Due to use of an unchecked chunk length, an unrecoverable fatal error can occur in versions prior to 1.1.10.1.

The code in the function hasNextChunk in the fileSnappyInputStream.java checks if a given stream has more chunks to read. It does that by attempting to read 4 bytes. If it wasn’t possible to read the 4 bytes, the function returns false. Otherwise, if 4 bytes were available, the code treats them as the length of the next chunk.

In the case that the `compressed` variable is null, a byte array is allocated with the size given by the input data. Since the code doesn’t test the legality of the `chunkSize` variable, it is possible to pass a negative number (such as 0xFFFFFFFF which is -1), which will cause the code to raise a `java.lang.NegativeArraySizeException` exception. A worse case would happen when passing a huge positive value (such as 0x7FFFFFFF), which would raise the fatal `java.lang.OutOfMemoryError` error.

Version 1.1.10.1 contains a patch for this issue.
CWE description: The product allocates a reusable resource or group of resources on behalf of an actor without imposing any restrictions on the size or number of resources that can be allocated, in violation of the intended security policy for that actor.
CVSS v3 base score: 7.5
Published: 2023-06-15T18:15Z
Repository: https://github.com/xerial/snappy-java
Language: Java
Files changed: src/main/java/org/xerial/snappy/SnappyInputStream.java, src/test/java/org/xerial/snappy/SnappyTest.java
Commit date: 2023-06-14 10:49:52 -0700
Commit message: Merge pull request from GHSA-qcwq-55hx-v3vh

* asserted chunksize should be in the bounds of 0-java.outofmmeoryexception

* asserted chunksize should be in the bounds of 0-java.outofmmeoryexception

* https://github.com/xerial/snappy-java-ghsa-qcwq-55hx-v3vh/pull/2

* advisory-fix-3

* added and changed method name for happy and sad cases in SnappyTest.java

* removed expected error for happy case in unit testing

* added another unit test case in SnappyTest.java and fixed comments in SnappyInputStream.java

* switched SnappyError to INVALID_CHUNK_SIZE

* Updated unit tests

* Resolved conflicts with another PR merge
Diff: diff --git a/src/main/java/org/xerial/snappy/SnappyInputStream.java b/src/main/java/org/xerial/snappy/SnappyInputStream.java
index 24f5976..9835cf9 100755
--- a/src/main/java/org/xerial/snappy/SnappyInputStream.java
+++ b/src/main/java/org/xerial/snappy/SnappyInputStream.java
@@ -415,13 +415,24 @@ public class SnappyInputStream
             else {
                 return false;
             }
         }
 
+        // chunkSize is negative
+        if (chunkSize < 0) {
+            throw new SnappyError(SnappyErrorCode.INVALID_CHUNK_SIZE, "chunkSize is too big or negative : " + chunkSize);
+        }
+
         // extend the compressed data buffer size
         if (compressed == null || chunkSize > compressed.length) {
-            compressed = new byte[chunkSize];
+            // chunkSize exceeds limit
+            try {
+                compressed = new byte[chunkSize];
+            }
+            catch (java.lang.OutOfMemoryError e) {
+                throw new SnappyError(SnappyErrorCode.INVALID_CHUNK_SIZE, e.getMessage());
+            }
         }
         readBytes = 0;
         while (readBytes < chunkSize) {
             int ret = in.read(compressed, readBytes, chunkSize - readBytes);
             if (ret == -1) {
diff --git a/src/test/java/org/xerial/snappy/SnappyTest.java b/src/test/java/org/xerial/snappy/SnappyTest.java
index 8a68b53..55726e7 100755
--- a/src/test/java/org/xerial/snappy/SnappyTest.java
+++ b/src/test/java/org/xerial/snappy/SnappyTest.java
@@ -24,10 +24,11 @@
 //--------------------------------------
 package org.xerial.snappy;
 
 import static org.junit.Assert.*;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 
 import org.junit.Assert;
 import org.junit.Test;
@@ -328,18 +329,65 @@ public class SnappyTest
         catch (IOException e) {
             _logger.debug(e);
         }
     }
 
+    /*
+
+    Tests happy cases for SnappyInputStream.read method
+    - {0}
+     */
+    @Test
+    public void isValidChunkLengthForSnappyInputStreamIn()
+            throws Exception {
+        byte[] data = {0};
+        SnappyInputStream in = new SnappyInputStream(new ByteArrayInputStream(data));
+        byte[] out = new byte[50];
+        in.read(out);
+    }
+
+    /*
+    Tests sad cases for SnappyInputStream.read method
+    - Expects a java.lang.NegativeArraySizeException catched into a SnappyError
+    - {-126, 'S', 'N', 'A', 'P', 'P', 'Y', 0, 0, 0, 0, 0, 0, 0, 0, 0,(byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff}
+     */
+    @Test(expected = SnappyError.class)
+    public void isInvalidChunkLengthForSnappyInputStreamInNegative()
+            throws Exception {
+        byte[] data = {-126, 'S', 'N', 'A', 'P', 'P', 'Y', 0, 0, 0, 0, 0, 0, 0, 0, 0,(byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff};
+        SnappyInputStream in = new SnappyInputStream(new ByteArrayInputStream(data));
+        byte[] out = new byte[50];
+        in.read(out);
+    }
+
+    /*
+    Tests sad cases for SnappyInputStream.read method
+    - Expects a java.lang.OutOfMemoryError
+    - {-126, 'S', 'N', 'A', 'P', 'P', 'Y', 0, 0, 0, 0, 0, 0, 0, 0, 0,(byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff}
+     */
+    @Test(expected = SnappyError.class)
+    public void isInvalidChunkLengthForSnappyInputStreamOutOfMemory()
+            throws Exception {
+        byte[] data = {-126, 'S', 'N', 'A', 'P', 'P', 'Y', 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff};
+        SnappyInputStream in = new SnappyInputStream(new ByteArrayInputStream(data));
+        byte[] out = new byte[50];
+        try {
+            in.read(out);
+        } catch (Exception ignored) {
+            // Exception here will be catched
+            // But OutOfMemoryError will not be caught, and will still be thrown
+        }
+    }
+
     /*
     Tests happy cases for BitShuffle.shuffle method
     - double: 0, 10
     - float: 0, 10
     - int: 0, 10
     - long: 0, 10
     - short: 0, 10
-     */
+    */
     @Test
     public void isValidArrayInputLengthForBitShuffleShuffle()
             throws Exception
     {
         byte[] b = BitShuffle.shuffle(new double[0]);
@@ -384,7 +432,8 @@ public class SnappyTest
     }
 
     @Test(expected = SnappyError.class)
     public void isTooLargeShortArrayInputLengthForBitShuffleShuffle() throws Exception {
         BitShuffle.shuffle(new short[Integer.MAX_VALUE / 2 + 1]);
+
     }
 }

Vulnerable code:             compressed = new byte[chunkSize];
     */
Fixed code:         // chunkSize is negative
        if (chunkSize < 0) {
            throw new SnappyError(SnappyErrorCode.INVALID_CHUNK_SIZE, "chunkSize is too big or negative : " + chunkSize);
        }

            // chunkSize exceeds limit
            try {
                compressed = new byte[chunkSize];
            }
            catch (java.lang.OutOfMemoryError e) {
                throw new SnappyError(SnappyErrorCode.INVALID_CHUNK_SIZE, e.getMessage());
            }
import java.io.ByteArrayInputStream;
    /*

    Tests happy cases for SnappyInputStream.read method
    - {0}
     */
    @Test
    public void isValidChunkLengthForSnappyInputStreamIn()
            throws Exception {
        byte[] data = {0};
        SnappyInputStream in = new SnappyInputStream(new ByteArrayInputStream(data));
        byte[] out = new byte[50];
        in.read(out);
    }

    /*
    Tests sad cases for SnappyInputStream.read method
    - Expects a java.lang.NegativeArraySizeException catched into a SnappyError
    - {-126, 'S', 'N', 'A', 'P', 'P', 'Y', 0, 0, 0, 0, 0, 0, 0, 0, 0,(byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff}
     */
    @Test(expected = SnappyError.class)
    public void isInvalidChunkLengthForSnappyInputStreamInNegative()
            throws Exception {
        byte[] data = {-126, 'S', 'N', 'A', 'P', 'P', 'Y', 0, 0, 0, 0, 0, 0, 0, 0, 0,(byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff};
        SnappyInputStream in = new SnappyInputStream(new ByteArrayInputStream(data));
        byte[] out = new byte[50];
        in.read(out);
    }

    /*
    Tests sad cases for SnappyInputStream.read method
    - Expects a java.lang.OutOfMemoryError
    - {-126, 'S', 'N', 'A', 'P', 'P', 'Y', 0, 0, 0, 0, 0, 0, 0, 0, 0,(byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff}
     */
    @Test(expected = SnappyError.class)
    public void isInvalidChunkLengthForSnappyInputStreamOutOfMemory()
            throws Exception {
        byte[] data = {-126, 'S', 'N', 'A', 'P', 'P', 'Y', 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff};
        SnappyInputStream in = new SnappyInputStream(new ByteArrayInputStream(data));
        byte[] out = new byte[50];
        try {
            in.read(out);
        } catch (Exception ignored) {
            // Exception here will be catched
            // But OutOfMemoryError will not be caught, and will still be thrown
        }
    }

    */