CVE-2023-34454 (published 2023-06-15T17:15Z)

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.\n\nThe 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.\n\nSince 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.\n\nSince 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.\n\nThe 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.\n\nVersion 1.1.10.1 contains a patch for this issue.

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-190
cwe_name: Integer Overflow or Wraparound
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.
extended_description: 
            
            
            
            
         
         

Fix information:
repo_url: https://github.com/xerial/snappy-java
commit_details: repo_url: https://github.com/xerial/snappy-java
author: aidanchiu1112
author_date: 2023-06-14 11:06:30-07:00
msg: 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>
repository: repo_url: https://github.com/xerial/snappy-java
repo_name: xerial/snappy-java
description: Snappy compressor/decompressor for Java
repo_language: Java
owner: xerial
file_changes: filename: Snappy.java
change_type: ModificationType.MODIFY
diff: @@ -169,7 +169,11 @@ public class Snappy
     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
     }
 
     /**
@@ -181,7 +185,11 @@ public class Snappy
     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
     }
 
     /**
@@ -193,7 +201,11 @@ public class Snappy
     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
     }
 
     /**
@@ -205,7 +217,11 @@ public class Snappy
     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
     }
 
     /**
@@ -217,7 +233,11 @@ public class Snappy
     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
     }
 
     /**
@@ -229,7 +249,11 @@ public class Snappy
     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
     }
 
     /**

programming_language: Java
filename: SnappyTest.java
change_type: ModificationType.MODIFY
diff: @@ -19,7 +19,7 @@
 // SnappyTest.java
 // Since: 2011/03/30
 //
-// $URL$ 
+// $URL$
 // $Author$
 //--------------------------------------
 package org.xerial.snappy;
@@ -331,8 +331,8 @@ public class SnappyTest
         }
     }
 
-    /*
 
+    /*
     Tests happy cases for SnappyInputStream.read method
     - {0}
      */
@@ -385,6 +385,67 @@ public class SnappyTest
     - 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()
@@ -435,5 +496,6 @@ public class SnappyTest
     public void isTooLargeShortArrayInputLengthForBitShuffleShuffle() throws Exception {
         BitShuffle.shuffle(new short[Integer.MAX_VALUE / 2 + 1]);
 
+
     }
 }

programming_language: Java