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
19 changes: 19 additions & 0 deletions src/java/net/jpountz/lz4/LZ4DecompressorWithLength.java
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,13 @@ private static void checkDestinationLength(int decompressedLength, int maxDestin
}
}

private static void checkCompressedLength(int compressedLength) {
if (compressedLength < 4) {
throw new LZ4Exception("Compressed length " + compressedLength
+ " is too short for the 4-byte length prefix");
}
}

private static int checkWritten(int written, int declared) {
if (written != declared) {
throw new LZ4Exception("Decompressed " + written
Expand Down Expand Up @@ -220,6 +227,8 @@ public int decompress(byte[] src, int srcOff, byte[] dest, int destOff) {
if (safeDecompressor != null) {
return decompress(src, srcOff, src.length - srcOff, dest, destOff);
}
SafeUtils.checkRange(src, srcOff, 0);
checkCompressedLength(src.length - srcOff);
final int destLen = getDecompressedLength(src, srcOff);
SafeUtils.checkRange(dest, destOff, 0);
checkDestinationLength(destLen, dest.length - destOff);
Expand All @@ -246,6 +255,8 @@ public int decompress(byte[] src, int srcOff, int srcLen, byte[] dest, int destO
if (safeDecompressor == null) {
return decompress(src, srcOff, dest, destOff);
}
SafeUtils.checkRange(src, srcOff, srcLen);
checkCompressedLength(srcLen);
final int destLen = getDecompressedLength(src, srcOff);
SafeUtils.checkRange(dest, destOff, 0);
checkDestinationLength(destLen, dest.length - destOff);
Expand Down Expand Up @@ -284,6 +295,8 @@ public byte[] decompress(byte[] src, int srcOff) {
if (safeDecompressor != null) {
return decompress(src, srcOff, src.length - srcOff);
}
SafeUtils.checkRange(src, srcOff, 0);
checkCompressedLength(src.length - srcOff);
final int destLen = getDecompressedLength(src, srcOff);
checkDecompressedLength(destLen, src.length - srcOff - 4);
return fastDecompressor.decompress(src, srcOff + 4, destLen);
Expand All @@ -310,6 +323,7 @@ public byte[] decompress(byte[] src, int srcOff, int srcLen) {
return decompress(src, srcOff);
}
SafeUtils.checkRange(src, srcOff, srcLen);
checkCompressedLength(srcLen);
final int destLen = getDecompressedLength(src, srcOff);
checkDecompressedLength(destLen, srcLen - 4);
final byte[] dest = new byte[destLen];
Expand All @@ -330,6 +344,7 @@ public byte[] decompress(byte[] src, int srcOff, int srcLen) {
* of <code>dest</code> after its position may have been partially overwritten
*/
public void decompress(ByteBuffer src, ByteBuffer dest) {
checkCompressedLength(src.remaining());
final int destLen = getDecompressedLength(src, src.position());
checkDestinationLength(destLen, dest.remaining());
if (safeDecompressor == null) {
Expand Down Expand Up @@ -363,6 +378,8 @@ public int decompress(ByteBuffer src, int srcOff, ByteBuffer dest, int destOff)
if (safeDecompressor != null) {
return decompress(src, srcOff, src.remaining() - srcOff, dest, destOff);
}
ByteBufferUtils.checkRange(src, srcOff, 0);
checkCompressedLength(src.limit() - srcOff);
final int destLen = getDecompressedLength(src, srcOff);
ByteBufferUtils.checkRange(dest, destOff, 0);
checkDestinationLength(destLen, dest.capacity() - destOff);
Expand Down Expand Up @@ -390,6 +407,8 @@ public int decompress(ByteBuffer src, int srcOff, int srcLen, ByteBuffer dest, i
if (safeDecompressor == null) {
return decompress(src, srcOff, dest, destOff);
}
ByteBufferUtils.checkRange(src, srcOff, srcLen);
checkCompressedLength(srcLen);
final int destLen = getDecompressedLength(src, srcOff);
ByteBufferUtils.checkRange(dest, destOff, 0);
checkDestinationLength(destLen, dest.capacity() - destOff);
Expand Down
53 changes: 53 additions & 0 deletions src/test/net/jpountz/lz4/OutOfBoundsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,59 @@ public void impossibleDeclaredLength(LZ4DecompressorWithLength decompressor) {
assertEquals("Decompressed length 67108865 exceeds configured maximum 67108864", exception.getMessage());
}

@ParameterizedTest
@MethodSource("decompressorsWithLength")
public void inputShorterThanLengthPrefix(LZ4DecompressorWithLength decompressor) {
for (int i = 0; i < 4; i++) {
int length = i;
byte[] src = new byte[length];
Arrays.fill(src, (byte) 1);
// the same bytes at an offset
byte[] padded = new byte[2 + length];
System.arraycopy(src, 0, padded, 2, length);
byte[] dest = new byte[16];

assertThrows(LZ4Exception.class, () -> decompressor.decompress(src));
assertThrows(LZ4Exception.class, () -> decompressor.decompress(src, 0));
assertThrows(LZ4Exception.class, () -> decompressor.decompress(padded, 2, length));
assertThrows(LZ4Exception.class, () -> decompressor.decompress(src, dest));
assertThrows(LZ4Exception.class, () -> decompressor.decompress(src, 0, dest, 0));
assertThrows(LZ4Exception.class, () -> decompressor.decompress(padded, 2, length, dest, 0));

for (boolean direct : new boolean[] {false, true}) {
ByteBuffer srcBuffer = direct ? ByteBuffer.allocateDirect(length) : ByteBuffer.allocate(length);
srcBuffer.put(src).flip();
ByteBuffer paddedBuffer = direct ? ByteBuffer.allocateDirect(padded.length) : ByteBuffer.allocate(padded.length);
paddedBuffer.put(padded).flip();
ByteBuffer destBuffer = direct ? ByteBuffer.allocateDirect(16) : ByteBuffer.allocate(16);

assertThrows(LZ4Exception.class, () -> decompressor.decompress(srcBuffer, destBuffer));
assertEquals(0, srcBuffer.position());
assertEquals(0, destBuffer.position());
assertThrows(LZ4Exception.class, () -> decompressor.decompress(srcBuffer, 0, destBuffer, 0));
assertThrows(LZ4Exception.class, () -> decompressor.decompress(paddedBuffer, 2, length, destBuffer, 0));
}
}
}

@Test
public void safeInputShorterThanLengthPrefixWithTrailingBytes() {
LZ4DecompressorWithLength decompressor =
new LZ4DecompressorWithLength(LZ4Factory.safeInstance().safeDecompressor());
// srcLen bytes are too short for the length prefix, even though src has more bytes after them
byte[] src = new byte[8];
byte[] dest = new byte[16];
for (int srcLen = 0; srcLen < 4; srcLen++) {
int len = srcLen;
assertThrows(LZ4Exception.class, () -> decompressor.decompress(src, 0, len));
assertThrows(LZ4Exception.class, () -> decompressor.decompress(src, 0, len, dest, 0));
assertThrows(LZ4Exception.class,
() -> decompressor.decompress(ByteBuffer.wrap(src), 0, len, ByteBuffer.wrap(dest), 0));
ByteBuffer limited = ByteBuffer.wrap(src, 0, len);
assertThrows(LZ4Exception.class, () -> decompressor.decompress(limited, ByteBuffer.wrap(dest)));
}
}

@ParameterizedTest
@MethodSource("decompressorsWithLength")
public void destinationBufferLimitsDecompressedLength(LZ4DecompressorWithLength decompressor) {
Expand Down
Loading