CVE-2023-34454: Integer Overflow or Wraparound (CWE-190)
value: snappy-java is a fast compressor/decompressor for Java. Due to unchecked multiplications, an integer overflow may occur in versions prior to 1.1.10.1, causing an unrecoverable fatal error.

The function `compress(char[] input)` in the file `Snappy.java` receives an array of characters and compresses it. It does so by multiplying the length by 2 and passing it to the rawCompress` function.

Since the length is not tested, the multiplication by two can cause an integer overflow and become negative. The rawCompress function then uses the received length and passes it to the natively compiled maxCompressedLength function, using the returned value to allocate a byte array.

Since the maxCompressedLength function treats the length as an unsigned integer, it doesn’t care that it is negative, and it returns a valid value, which is casted to a signed integer by the Java engine. If the result is negative, a `java.lang.NegativeArraySizeException` exception will be raised while trying to allocate the array `buf`. On the other side, if the result is positive, the `buf` array will successfully be allocated, but its size might be too small to use for the compression, causing a fatal Access Violation error.

The same issue exists also when using the `compress` functions that receive double, float, int, long and short, each using a different multiplier that may cause the same issue. The issue most likely won’t occur when using a byte array, since creating a byte array of size 0x80000000 (or any other negative value) is impossible in the first place.

Version 1.1.10.1 contains a patch for this issue.
CWE description: The product performs a calculation that can
         produce an integer overflow or wraparound when the logic
         assumes that the resulting value will always be larger than
         the original value. This occurs when an integer value is
         incremented to a value that is too large to store in the
         associated representation. When this occurs, the value may
         become a very small or negative number.
CVSS v3 base score: 7.5
Published: 2023-06-15T17:15Z
Repository: https://github.com/xerial/snappy-java
Language: Java
Files changed: src/main/java/org/xerial/snappy/Snappy.java, src/test/java/org/xerial/snappy/SnappyTest.java
Commit date: 2023-06-14 11:06:30 -0700
Commit message: Merge pull request from GHSA-fjpj-2g6w-x25r

* Fixed integer overflow by checking if bytesize is bigger than input length, then throwing exception

* Fixed integer overflow by checking if bytesize is bigger than input length, then throwing exception

* Fixed integer overflow by checking if bytesize is bigger than input length, then throwing exception

* improved error messages by adding new error enum INPUT_TOO_LARGE in SnappyErrorCode.java, and added happy and sad cases in SnappyTest.java

* fixed mispelling: validArrayInputLength --> isInvalidArrayInputLength

* switched SnappyError into ILLEGAL_ARGUMENT in SnappyErrorCode.java and Snappy.java and fixed a typo in error comment

* Fix buffer size boundary tests

* Remove negative array size tests

* updated comments for unit test

---------

Co-authored-by: Taro L. Saito <leo@xerial.org>
Diff: diff --git a/src/main/java/org/xerial/snappy/Snappy.java b/src/main/java/org/xerial/snappy/Snappy.java
index 5263e94..14d1796 100755
--- a/src/main/java/org/xerial/snappy/Snappy.java
+++ b/src/main/java/org/xerial/snappy/Snappy.java
@@ -167,11 +167,15 @@ public class Snappy
      * @return the compressed data
      */
     public static byte[] compress(char[] input)
             throws IOException
     {
-        return rawCompress(input, input.length * 2); // char uses 2 bytes
+        int byteSize = input.length * 2;
+        if (byteSize < input.length) {
+            throw new SnappyError(SnappyErrorCode.TOO_LARGE_INPUT, "input array size is too large: " + input.length);
+        }
+        return rawCompress(input, byteSize); // char uses 2 bytes
     }
 
     /**
      * Compress the input double array
      *
@@ -179,11 +183,15 @@ public class Snappy
      * @return the compressed data
      */
     public static byte[] compress(double[] input)
             throws IOException
     {
-        return rawCompress(input, input.length * 8); // double uses 8 bytes
+        int byteSize = input.length * 8;
+        if (byteSize < input.length) {
+            throw new SnappyError(SnappyErrorCode.TOO_LARGE_INPUT, "input array size is too large: " + input.length);
+        }
+        return rawCompress(input, byteSize); // double uses 8 bytes
     }
 
     /**
      * Compress the input float array
      *
@@ -191,11 +199,15 @@ public class Snappy
      * @return the compressed data
      */
     public static byte[] compress(float[] input)
             throws IOException
     {
-        return rawCompress(input, input.length * 4); // float uses 4 bytes
+        int byteSize = input.length * 4;
+        if (byteSize < input.length) {
+            throw new SnappyError(SnappyErrorCode.TOO_LARGE_INPUT, "input array size is too large: " + input.length);
+        }
+        return rawCompress(input, byteSize); // float uses 4 bytes
     }
 
     /**
      * Compress the input int array
      *
@@ -203,11 +215,15 @@ public class Snappy
      * @return the compressed data
      */
     public static byte[] compress(int[] input)
             throws IOException
     {
-        return rawCompress(input, input.length * 4); // int uses 4 bytes
+        int byteSize = input.length * 4;
+        if (byteSize < input.length) {
+            throw new SnappyError(SnappyErrorCode.TOO_LARGE_INPUT, "input array size is too large: " + input.length);
+        }
+        return rawCompress(input, byteSize); // int uses 4 bytes
     }
 
     /**
      * Compress the input long array
      *
@@ -215,11 +231,15 @@ public class Snappy
      * @return the compressed data
      */
     public static byte[] compress(long[] input)
             throws IOException
     {
-        return rawCompress(input, input.length * 8); // long uses 8 bytes
+        int byteSize = input.length * 8;
+        if (byteSize < input.length) {
+            throw new SnappyError(SnappyErrorCode.TOO_LARGE_INPUT, "input array size is too large: " + input.length);
+        }
+        return rawCompress(input, byteSize); // long uses 8 bytes
     }
 
     /**
      * Compress the input short array
      *
@@ -227,11 +247,15 @@ public class Snappy
      * @return the compressed data
      */
     public static byte[] compress(short[] input)
             throws IOException
     {
-        return rawCompress(input, input.length * 2); // short uses 2 bytes
+        int byteSize = input.length * 2;
+        if (byteSize < input.length) {
+            throw new SnappyError(SnappyErrorCode.TOO_LARGE_INPUT, "input array size is too large: " + input.length);
+        }
+        return rawCompress(input, byteSize); // short uses 2 bytes
     }
 
     /**
      * Compress the input String
      *
diff --git a/src/test/java/org/xerial/snappy/SnappyTest.java b/src/test/java/org/xerial/snappy/SnappyTest.java
index 55726e7..a9fa9ea 100755
--- a/src/test/java/org/xerial/snappy/SnappyTest.java
+++ b/src/test/java/org/xerial/snappy/SnappyTest.java
@@ -17,11 +17,11 @@
 // snappy-java Project
 //
 // SnappyTest.java
 // Since: 2011/03/30
 //
-// $URL$ 
+// $URL$
 // $Author$
 //--------------------------------------
 package org.xerial.snappy;
 
 import static org.junit.Assert.*;
@@ -329,12 +329,12 @@ public class SnappyTest
         catch (IOException e) {
             _logger.debug(e);
         }
     }
 
-    /*
 
+    /*
     Tests happy cases for SnappyInputStream.read method
     - {0}
      */
     @Test
     public void isValidChunkLengthForSnappyInputStreamIn()
@@ -383,10 +383,71 @@ public class SnappyTest
     - double: 0, 10
     - float: 0, 10
     - int: 0, 10
     - long: 0, 10
     - short: 0, 10
+     */
+    @Test
+    public void isValidArrayInputLength()
+            throws Exception {
+        byte[] a = Snappy.compress(new char[0]);
+        byte[] b = Snappy.compress(new double[0]);
+        byte[] c = Snappy.compress(new float[0]);
+        byte[] d = Snappy.compress(new int[0]);
+        byte[] e = Snappy.compress(new long[0]);
+        byte[] f = Snappy.compress(new short[0]);
+        byte[] g = Snappy.compress(new char[10]);
+        byte[] h = Snappy.compress(new double[10]);
+        byte[] i = Snappy.compress(new float[10]);
+        byte[] j = Snappy.compress(new int[10]);
+        byte[] k = Snappy.compress(new long[10]);
+        byte[] l = Snappy.compress(new short[10]);
+    }
+
+    /*
+    Tests sad cases for Snappy.compress
+    - Allocate a buffer whose byte size will be a bit larger than Integer.MAX_VALUE
+    - char
+    - double
+    - float
+    - int
+    - long
+    - short
+     */
+    @Test(expected = SnappyError.class)
+    public void isTooLargeDoubleArrayInputLength() throws Exception {
+        Snappy.compress(new double[Integer.MAX_VALUE / 8 + 1]);
+    }
+
+    @Test(expected = SnappyError.class)
+    public void isTooLargeCharArrayInputLength() throws Exception {
+        Snappy.compress(new char[Integer.MAX_VALUE / 2 + 1]);
+    }
+
+    @Test(expected = SnappyError.class)
+    public void isTooLargeFloatArrayInputLength() throws Exception {
+        Snappy.compress(new float[Integer.MAX_VALUE / 4 + 1]);
+    }
+
+    @Test(expected = SnappyError.class)
+    public void isTooLargeIntArrayInputLength() throws Exception {
+        Snappy.compress(new int[Integer.MAX_VALUE / 4 + 1]);
+    }
+
+    @Test(expected = SnappyError.class)
+    public void isTooLargeLongArrayInputLength() throws Exception {
+        Snappy.compress(new long[Integer.MAX_VALUE / 8 + 1]);
+    }
+
+    @Test(expected = SnappyError.class)
+    public void isTooLargeShortArrayInputLength() throws Exception {
+        Snappy.compress(new short[Integer.MAX_VALUE / 2 + 1]);
+    }
+
+    /*
+    Tests happy cases for Snappy.compress
+    - char: 0, 10
     */
     @Test
     public void isValidArrayInputLengthForBitShuffleShuffle()
             throws Exception
     {
@@ -433,7 +494,8 @@ public class SnappyTest
 
     @Test(expected = SnappyError.class)
     public void isTooLargeShortArrayInputLengthForBitShuffleShuffle() throws Exception {
         BitShuffle.shuffle(new short[Integer.MAX_VALUE / 2 + 1]);
 
+
     }
 }

Vulnerable code:         return rawCompress(input, input.length * 2); // char uses 2 bytes
        return rawCompress(input, input.length * 8); // double uses 8 bytes
        return rawCompress(input, input.length * 4); // float uses 4 bytes
        return rawCompress(input, input.length * 4); // int uses 4 bytes
        return rawCompress(input, input.length * 8); // long uses 8 bytes
        return rawCompress(input, input.length * 2); // short uses 2 bytes
// $URL$ 
    /*
Fixed code:         int byteSize = input.length * 2;
        if (byteSize < input.length) {
            throw new SnappyError(SnappyErrorCode.TOO_LARGE_INPUT, "input array size is too large: " + input.length);
        }
        return rawCompress(input, byteSize); // char uses 2 bytes
        int byteSize = input.length * 8;
        if (byteSize < input.length) {
            throw new SnappyError(SnappyErrorCode.TOO_LARGE_INPUT, "input array size is too large: " + input.length);
        }
        return rawCompress(input, byteSize); // double uses 8 bytes
        int byteSize = input.length * 4;
        if (byteSize < input.length) {
            throw new SnappyError(SnappyErrorCode.TOO_LARGE_INPUT, "input array size is too large: " + input.length);
        }
        return rawCompress(input, byteSize); // float uses 4 bytes
        int byteSize = input.length * 4;
        if (byteSize < input.length) {
            throw new SnappyError(SnappyErrorCode.TOO_LARGE_INPUT, "input array size is too large: " + input.length);
        }
        return rawCompress(input, byteSize); // int uses 4 bytes
        int byteSize = input.length * 8;
        if (byteSize < input.length) {
            throw new SnappyError(SnappyErrorCode.TOO_LARGE_INPUT, "input array size is too large: " + input.length);
        }
        return rawCompress(input, byteSize); // long uses 8 bytes
        int byteSize = input.length * 2;
        if (byteSize < input.length) {
            throw new SnappyError(SnappyErrorCode.TOO_LARGE_INPUT, "input array size is too large: " + input.length);
        }
        return rawCompress(input, byteSize); // short uses 2 bytes
// $URL$
    /*
     */
    @Test
    public void isValidArrayInputLength()
            throws Exception {
        byte[] a = Snappy.compress(new char[0]);
        byte[] b = Snappy.compress(new double[0]);
        byte[] c = Snappy.compress(new float[0]);
        byte[] d = Snappy.compress(new int[0]);
        byte[] e = Snappy.compress(new long[0]);
        byte[] f = Snappy.compress(new short[0]);
        byte[] g = Snappy.compress(new char[10]);
        byte[] h = Snappy.compress(new double[10]);
        byte[] i = Snappy.compress(new float[10]);
        byte[] j = Snappy.compress(new int[10]);
        byte[] k = Snappy.compress(new long[10]);
        byte[] l = Snappy.compress(new short[10]);
    }

    /*
    Tests sad cases for Snappy.compress
    - Allocate a buffer whose byte size will be a bit larger than Integer.MAX_VALUE
    - char
    - double
    - float
    - int
    - long
    - short
     */
    @Test(expected = SnappyError.class)
    public void isTooLargeDoubleArrayInputLength() throws Exception {
        Snappy.compress(new double[Integer.MAX_VALUE / 8 + 1]);
    }

    @Test(expected = SnappyError.class)
    public void isTooLargeCharArrayInputLength() throws Exception {
        Snappy.compress(new char[Integer.MAX_VALUE / 2 + 1]);
    }

    @Test(expected = SnappyError.class)
    public void isTooLargeFloatArrayInputLength() throws Exception {
        Snappy.compress(new float[Integer.MAX_VALUE / 4 + 1]);
    }

    @Test(expected = SnappyError.class)
    public void isTooLargeIntArrayInputLength() throws Exception {
        Snappy.compress(new int[Integer.MAX_VALUE / 4 + 1]);
    }

    @Test(expected = SnappyError.class)
    public void isTooLargeLongArrayInputLength() throws Exception {
        Snappy.compress(new long[Integer.MAX_VALUE / 8 + 1]);
    }

    @Test(expected = SnappyError.class)
    public void isTooLargeShortArrayInputLength() throws Exception {
        Snappy.compress(new short[Integer.MAX_VALUE / 2 + 1]);
    }

    /*
    Tests happy cases for Snappy.compress
    - char: 0, 10