From 3225f09a7f098b179c2c03c3b015aaec519e5a87 Mon Sep 17 00:00:00 2001 From: iliasabk Date: Thu, 17 Sep 2026 10:23:21 +0200 Subject: [PATCH] Add alignment check to typed uncompress*Array methods The six typed uncompress*Array methods size the result array by integer-dividing the declared uncompressed byte length by the element size, then pass the undivided length to native rawUncompress. When the declared length is not a multiple of the element size, the array is under-allocated by up to (elementSize - 1) bytes while native writes the full amount: heap buffer overflow. Reject non-multiple lengths with IOException before allocation. Fixes https://github.com/xerial/snappy-java/issues/729 --- src/main/java/org/xerial/snappy/Snappy.java | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/java/org/xerial/snappy/Snappy.java b/src/main/java/org/xerial/snappy/Snappy.java index 14d17964..0733b9a2 100755 --- a/src/main/java/org/xerial/snappy/Snappy.java +++ b/src/main/java/org/xerial/snappy/Snappy.java @@ -608,6 +608,7 @@ public static char[] uncompressCharArray(byte[] input, int offset, int length) throws IOException { int uncompressedLength = Snappy.uncompressedLength(input, offset, length); + validateUncompressedLength(uncompressedLength, 2); char[] result = new char[uncompressedLength / 2]; impl.rawUncompress(input, offset, length, result, 0); return result; @@ -639,6 +640,7 @@ public static double[] uncompressDoubleArray(byte[] input, int offset, int lengt throws IOException { int uncompressedLength = Snappy.uncompressedLength(input, offset, length); + validateUncompressedLength(uncompressedLength, 8); double[] result = new double[uncompressedLength / 8]; impl.rawUncompress(input, offset, length, result, 0); return result; @@ -741,6 +743,7 @@ public static float[] uncompressFloatArray(byte[] input, int offset, int length) throws IOException { int uncompressedLength = Snappy.uncompressedLength(input, offset, length); + validateUncompressedLength(uncompressedLength, 4); float[] result = new float[uncompressedLength / 4]; impl.rawUncompress(input, offset, length, result, 0); return result; @@ -772,6 +775,7 @@ public static int[] uncompressIntArray(byte[] input, int offset, int length) throws IOException { int uncompressedLength = Snappy.uncompressedLength(input, offset, length); + validateUncompressedLength(uncompressedLength, 4); int[] result = new int[uncompressedLength / 4]; impl.rawUncompress(input, offset, length, result, 0); return result; @@ -803,6 +807,7 @@ public static long[] uncompressLongArray(byte[] input, int offset, int length) throws IOException { int uncompressedLength = Snappy.uncompressedLength(input, offset, length); + validateUncompressedLength(uncompressedLength, 8); long[] result = new long[uncompressedLength / 8]; impl.rawUncompress(input, offset, length, result, 0); return result; @@ -834,11 +839,28 @@ public static short[] uncompressShortArray(byte[] input, int offset, int length) throws IOException { int uncompressedLength = Snappy.uncompressedLength(input, offset, length); + validateUncompressedLength(uncompressedLength, 2); short[] result = new short[uncompressedLength / 2]; impl.rawUncompress(input, offset, length, result, 0); return result; } + /** + * Reject an uncompressed byte length that is not a multiple of the target + * element size. The typed uncompress*Array methods size the result array + * by integer division; without this check a non-multiple length would + * under-allocate the array while native code writes the full byte count. + */ + private static void validateUncompressedLength(int uncompressedLength, int elementSize) + throws IOException + { + if (uncompressedLength % elementSize != 0) { + throw new IOException( + "uncompressed length " + uncompressedLength + + " is not a multiple of the element size " + elementSize); + } + } + /** * Uncompress the input as a String *