Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 36 additions & 17 deletions src/main/java/org/xerial/snappy/Snappy.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ static void init() {
public static void arrayCopy(Object src, int offset, int byteLength, Object dest, int dest_offset)
throws IOException
{
if (src == null || dest == null) {
throw new NullPointerException("src or dest is null");
}
checkArrayRange(src, offset, byteLength, "src");
checkArrayRange(dest, dest_offset, byteLength, "dest");
impl.arrayCopy(src, offset, byteLength, dest, dest_offset);
}

Expand Down Expand Up @@ -152,7 +157,7 @@ public static int compress(ByteBuffer uncompressed, ByteBuffer compressed)
int uPos = uncompressed.position();
int uLen = uncompressed.remaining();
int cPos = compressed.position();
checkOutputSpace(compressed.remaining(), 0, requiredCompressedLength(uLen));
checkOutputSpace(compressed.remaining(), 0, maxCompressedLength(uLen));
int compressedSize = impl.rawCompress(uncompressed, uPos, uLen, compressed,
cPos);

Expand Down Expand Up @@ -404,10 +409,21 @@ public static boolean isValidCompressedBuffer(long inputAddr, long offset, long
*
* @param byteSize byte size of the data to compress
* @return maximum byte size of the compressed data
* @throws IllegalArgumentException if byteSize is negative
* @throws SnappyError ({@link SnappyErrorCode#TOO_LARGE_INPUT}) if the maximum compressed size exceeds
* Integer.MAX_VALUE
*/
public static int maxCompressedLength(int byteSize)
{
return impl.maxCompressedLength(byteSize);
if (byteSize < 0) {
throw new IllegalArgumentException("byteSize must not be negative: " + byteSize);
}
int maxCompressedLength = impl.maxCompressedLength(byteSize);
// The native result overflows int for inputs close to 2GB
if (maxCompressedLength < 0) {
throw new SnappyError(SnappyErrorCode.TOO_LARGE_INPUT, "input is too large to compress: " + byteSize);
}
return maxCompressedLength;
}

/**
Expand Down Expand Up @@ -454,7 +470,7 @@ public static byte[] rawCompress(Object data, int byteSize)
throw new NullPointerException("input is null");
}
checkArrayRange(data, 0, byteSize, "input");
byte[] buf = new byte[requiredCompressedLength(byteSize)];
byte[] buf = new byte[maxCompressedLength(byteSize)];
int compressedByteSize = impl.rawCompress(data, 0, byteSize, buf, 0);
byte[] result = new byte[compressedByteSize];
System.arraycopy(buf, 0, result, 0, compressedByteSize);
Expand Down Expand Up @@ -482,7 +498,7 @@ public static int rawCompress(Object input, int inputOffset, int inputLength, by
throw new NullPointerException("input or output is null");
}
checkArrayRange(input, inputOffset, inputLength, "input");
checkOutputSpace(output.length, outputOffset, requiredCompressedLength(inputLength));
checkOutputSpace(output.length, outputOffset, maxCompressedLength(inputLength));

int compressedSize = impl
.rawCompress(input, inputOffset, inputLength, output, outputOffset);
Expand Down Expand Up @@ -567,6 +583,22 @@ private static void checkArrayRange(Object array, int offset, int length, String
}
}

/**
* Ensures the element range [off, off+len) lies within a typed array of arrayLength elements, and that its byte
* offset and length fit in int, before converting them to a byte range.
*/
static void checkElementRange(int arrayLength, int off, int len, int elementSize)
{
if (off < 0 || len < 0 || off > arrayLength - len) {
throw new IndexOutOfBoundsException(String.format(
"range [off:%,d, len:%,d] is out of bounds for array of length %,d", off, len, arrayLength));
}
if ((long) (off + len) * elementSize > Integer.MAX_VALUE) {
throw new SnappyError(SnappyErrorCode.TOO_LARGE_INPUT, String.format(
"byte size of range [off:%,d, len:%,d] exceeds Integer.MAX_VALUE", off, len));
}
}

/**
* Ensures the output has at least requiredSize bytes available after outputOffset, since the native code writes
* the output through a raw pointer without any bounds check.
Expand All @@ -584,19 +616,6 @@ private static void checkOutputSpace(long outputSize, long outputOffset, long re
}
}

/**
* Returns the output buffer size required by the native compressor for the given input size.
*/
private static int requiredCompressedLength(int inputLength)
{
int maxCompressedLength = maxCompressedLength(inputLength);
// maxCompressedLength overflows int for inputs close to 2GB
if (maxCompressedLength < 0) {
throw new SnappyError(SnappyErrorCode.TOO_LARGE_INPUT, "input is too large to compress: " + inputLength);
}
return maxCompressedLength;
}

/**
* Rejects an uncompressed length declared in a compressed stream header that no valid Snappy stream of the
* given compressed size can produce. The densest Snappy element is a 3-byte copy that expands to 64 bytes, so
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/org/xerial/snappy/SnappyInputStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ protected void readFully(byte[] fragment, int fragmentLength)
public int read(byte[] b, int byteOffset, int byteLength)
throws IOException
{
Snappy.checkElementRange(b.length, byteOffset, byteLength, 1);
int writtenBytes = 0;
for (; writtenBytes < byteLength; ) {

Expand Down Expand Up @@ -243,6 +244,7 @@ public int rawRead(Object array, int byteOffset, int byteLength)
public int read(long[] d, int off, int len)
throws IOException
{
Snappy.checkElementRange(d.length, off, len, 8);
return rawRead(d, off * 8, len * 8);
}

Expand Down Expand Up @@ -273,6 +275,7 @@ public int read(long[] d)
public int read(double[] d, int off, int len)
throws IOException
{
Snappy.checkElementRange(d.length, off, len, 8);
return rawRead(d, off * 8, len * 8);
}

Expand Down Expand Up @@ -317,6 +320,7 @@ public int read(int[] d)
public int read(int[] d, int off, int len)
throws IOException
{
Snappy.checkElementRange(d.length, off, len, 4);
return rawRead(d, off * 4, len * 4);
}

Expand All @@ -333,6 +337,7 @@ public int read(int[] d, int off, int len)
public int read(float[] d, int off, int len)
throws IOException
{
Snappy.checkElementRange(d.length, off, len, 4);
return rawRead(d, off * 4, len * 4);
}

Expand Down Expand Up @@ -363,6 +368,7 @@ public int read(float[] d)
public int read(short[] d, int off, int len)
throws IOException
{
Snappy.checkElementRange(d.length, off, len, 2);
return rawRead(d, off * 2, len * 2);
}

Expand Down
6 changes: 6 additions & 0 deletions src/main/java/org/xerial/snappy/SnappyOutputStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ public void write(byte[] b, int byteOffset, int byteLength)
if (closed) {
throw new IOException("Stream is closed");
}
Snappy.checkElementRange(b.length, byteOffset, byteLength, 1);
int cursor = 0;
while (cursor < byteLength) {
int readLen = Math.min(byteLength - cursor, blockSize - inputCursor);
Expand Down Expand Up @@ -147,6 +148,7 @@ public void write(byte[] b, int byteOffset, int byteLength)
public void write(long[] d, int off, int len)
throws IOException
{
Snappy.checkElementRange(d.length, off, len, 8);
rawWrite(d, off * 8, len * 8);
}

Expand All @@ -161,6 +163,7 @@ public void write(long[] d, int off, int len)
public void write(double[] f, int off, int len)
throws IOException
{
Snappy.checkElementRange(f.length, off, len, 8);
rawWrite(f, off * 8, len * 8);
}

Expand All @@ -175,6 +178,7 @@ public void write(double[] f, int off, int len)
public void write(float[] f, int off, int len)
throws IOException
{
Snappy.checkElementRange(f.length, off, len, 4);
rawWrite(f, off * 4, len * 4);
}

Expand All @@ -189,6 +193,7 @@ public void write(float[] f, int off, int len)
public void write(int[] f, int off, int len)
throws IOException
{
Snappy.checkElementRange(f.length, off, len, 4);
rawWrite(f, off * 4, len * 4);
}

Expand All @@ -203,6 +208,7 @@ public void write(int[] f, int off, int len)
public void write(short[] f, int off, int len)
throws IOException
{
Snappy.checkElementRange(f.length, off, len, 2);
rawWrite(f, off * 2, len * 2);
}

Expand Down
87 changes: 87 additions & 0 deletions src/test/java/org/xerial/snappy/SnappyBoundsCheckTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -332,4 +332,91 @@ public void framedStreamWithHugeDeclaredUncompressedLength()
assertEquals(SnappyErrorCode.PARSING_ERROR, e.getErrorCode());
}
}

// https://github.com/xerial/snappy-java/issues/671
@Test
public void maxCompressedLengthOverflow()
throws Exception
{
// 32 + n + n / 6 is Integer.MAX_VALUE for n = 1840700242
assertEquals(Integer.MAX_VALUE, Snappy.maxCompressedLength(1840700242));
try {
Snappy.maxCompressedLength(1840700243);
fail("expected SnappyError");
}
catch (SnappyError e) {
assertEquals(SnappyErrorCode.TOO_LARGE_INPUT, e.errorCode);
}
}

@Test(expected = IllegalArgumentException.class)
public void maxCompressedLengthOfNegativeSize()
throws Exception
{
Snappy.maxCompressedLength(-1);
}

@Test(expected = IllegalArgumentException.class)
public void arrayCopyOutOfRange()
throws Exception
{
Snappy.arrayCopy(new int[4], 0, 17, new byte[100], 0);
}

// https://github.com/xerial/snappy-java/issues/513
@Test(expected = IllegalArgumentException.class)
public void outputStreamRawWriteOutOfRange()
throws Exception
{
SnappyOutputStream out = new SnappyOutputStream(new ByteArrayOutputStream());
out.rawWrite(new int[4], 0, 1024);
}

@Test(expected = IndexOutOfBoundsException.class)
public void outputStreamWriteTypedArrayOutOfRange()
throws Exception
{
SnappyOutputStream out = new SnappyOutputStream(new ByteArrayOutputStream());
out.write(new long[4], 2, 3);
}

@Test(expected = SnappyError.class)
public void elementRangeWithOverflowingByteSize()
{
// a long[] range whose byte offset overflows int; called directly to avoid allocating a 2GB array
int length = Integer.MAX_VALUE / 8 + 2;
Snappy.checkElementRange(length, length - 1, 1, 8);
}

@Test(expected = IndexOutOfBoundsException.class)
public void outputStreamWriteBytesOutOfRange()
throws Exception
{
SnappyOutputStream out = new SnappyOutputStream(new ByteArrayOutputStream());
out.write(new byte[10], 5, 10);
}

@Test(expected = IndexOutOfBoundsException.class)
public void inputStreamReadTypedArrayOutOfRange()
throws Exception
{
ByteArrayOutputStream compressed = new ByteArrayOutputStream();
SnappyOutputStream out = new SnappyOutputStream(compressed);
out.write(new int[100]);
out.close();
SnappyInputStream in = new SnappyInputStream(new ByteArrayInputStream(compressed.toByteArray()));
in.read(new int[10], 5, 10);
}

@Test(expected = IllegalArgumentException.class)
public void inputStreamRawReadOutOfRange()
throws Exception
{
ByteArrayOutputStream compressed = new ByteArrayOutputStream();
SnappyOutputStream out = new SnappyOutputStream(compressed);
out.write(new int[100]);
out.close();
SnappyInputStream in = new SnappyInputStream(new ByteArrayInputStream(compressed.toByteArray()));
in.rawRead(new int[10], 0, 400);
}
}
Loading