CVE-2023-34454 (Java, published 2023-06-15T17:15Z)
Description: 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.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-190 \u2014 Integer Overflow or Wraparound
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.
Repository: https://github.com/xerial/snappy-java
Files changed:
src/main/java/org/xerial/snappy/Snappy.java
src/test/java/org/xerial/snappy/SnappyTest.java
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>

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