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
6 changes: 6 additions & 0 deletions src/java/net/jpountz/lz4/LZ4CompressorWithLength.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
* {@link LZ4DecompressorWithLength} and is NOT compatible with any other
* decompressors in lz4-java or any other lz4 tools. This class deliberately
* does not extend {@link LZ4Compressor} because they are not interchangable.
* Methods in {@link LZ4DecompressorWithLength} that allocate their output
* buffer reject decompressed lengths greater than 64 MiB by default. For large
* or unknown-size inputs, prefer {@link LZ4FrameOutputStream} to avoid buffering
* the complete input in memory. The default can be overridden with the
* {@code net.jpountz.lz4.LZ4DecompressorWithLength.maxDecompressedLength}
* system property.
*/

public class LZ4CompressorWithLength {
Expand Down
44 changes: 34 additions & 10 deletions src/java/net/jpountz/lz4/LZ4DecompressorWithLength.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,21 @@
* The user does not need to specify the length of the compressed data or
* original data because the length of the original decompressed data is
* included in the compressed data.
* For large or unknown-size inputs, prefer {@link LZ4FrameInputStream} to avoid
* buffering the complete decompressed data in memory.
* The default maximum decompressed length for methods that allocate their
* output buffer can be overridden at class initialization with the
* {@code net.jpountz.lz4.LZ4DecompressorWithLength.maxDecompressedLength}
* system property, specified as a number of bytes. Constructors with an
* explicit maximum are not affected by this property.
Comment thread
yawkat marked this conversation as resolved.
*/

public class LZ4DecompressorWithLength {

// Each LZ4 match-length extension byte adds at most 255 decompressed bytes.
private static final int MAX_COMPRESSION_RATIO = 255;
private static final int DEFAULT_MAX_DECOMPRESSED_LENGTH = 64 * 1024 * 1024;
private static final int DEFAULT_MAX_DECOMPRESSED_LENGTH = Integer.getInteger(
"net.jpountz.lz4.LZ4DecompressorWithLength.maxDecompressedLength", 64 * 1024 * 1024);
Comment thread
yawkat marked this conversation as resolved.

private final LZ4FastDecompressor fastDecompressor;
private final LZ4SafeDecompressor safeDecompressor;
Expand Down Expand Up @@ -84,7 +92,9 @@ public static int getDecompressedLength(ByteBuffer src, int srcOff) {

/**
* Creates a new decompressor to decompress data compressed by {@link LZ4CompressorWithLength}.
* Methods that allocate their output buffer reject decompressed lengths greater than 64 MiB.
* Methods that allocate their output buffer reject decompressed lengths greater than 64 MiB by default.
* This fallback can be overridden with the
* {@code net.jpountz.lz4.LZ4DecompressorWithLength.maxDecompressedLength} system property.
* Note that it is deprecated to use a JNI-binding instance of {@link LZ4FastDecompressor}.
* Please see {@link LZ4Factory#nativeInstance()} for details.
*
Expand Down Expand Up @@ -113,7 +123,9 @@ public LZ4DecompressorWithLength(LZ4FastDecompressor fastDecompressor, int maxDe

/**
* Creates a new decompressor to decompress data compressed by {@link LZ4CompressorWithLength}.
* Methods that allocate their output buffer reject decompressed lengths greater than 64 MiB.
* Methods that allocate their output buffer reject decompressed lengths greater than 64 MiB by default.
* This fallback can be overridden with the
* {@code net.jpountz.lz4.LZ4DecompressorWithLength.maxDecompressedLength} system property.
*
* @param safeDecompressor safe decompressor to use
*/
Expand All @@ -137,22 +149,34 @@ public LZ4DecompressorWithLength(LZ4SafeDecompressor safeDecompressor, int maxDe
}

private void checkDecompressedLength(int decompressedLength) {
if (decompressedLength < 0 || decompressedLength > maxDecompressedLength) {
throw new LZ4Exception("Invalid decompressed length");
if (decompressedLength < 0) {
throw new LZ4Exception("Invalid decompressed length: " + decompressedLength);
}
if (decompressedLength > maxDecompressedLength) {
throw new LZ4Exception("Decompressed length " + decompressedLength
+ " exceeds configured maximum " + maxDecompressedLength);
}
}

private void checkDecompressedLength(int decompressedLength, int compressedLength) {
if (compressedLength < 0
|| decompressedLength > (long) compressedLength * MAX_COMPRESSION_RATIO) {
throw new LZ4Exception("Invalid decompressed length");
if (compressedLength < 0) {
throw new LZ4Exception("Invalid compressed length: " + compressedLength);
}
if (decompressedLength > (long) compressedLength * MAX_COMPRESSION_RATIO) {
throw new LZ4Exception("Decompressed length " + decompressedLength
+ " exceeds maximum compression ratio of " + MAX_COMPRESSION_RATIO
+ " for compressed length " + compressedLength);
}
checkDecompressedLength(decompressedLength);
}

private static void checkDestinationLength(int decompressedLength, int maxDestinationLength) {
if (decompressedLength < 0 || decompressedLength > maxDestinationLength) {
throw new LZ4Exception("Invalid decompressed length");
if (decompressedLength < 0) {
throw new LZ4Exception("Invalid decompressed length: " + decompressedLength);
}
if (decompressedLength > maxDestinationLength) {
throw new LZ4Exception("Decompressed length " + decompressedLength
+ " exceeds destination length " + maxDestinationLength);
}
}

Expand Down
15 changes: 8 additions & 7 deletions src/test/net/jpountz/lz4/OutOfBoundsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,12 @@ public void impossibleDeclaredLength(LZ4DecompressorWithLength decompressor) {
0 // empty LZ4 block
};
LZ4Exception exception = assertThrows(LZ4Exception.class, () -> decompressor.decompress(input));
assertEquals("Invalid decompressed length", exception.getMessage());
assertEquals("Decompressed length 1048576 exceeds maximum compression ratio of 255 for compressed length 1",
exception.getMessage());

Arrays.fill(input, 0, 4, (byte) 0xff); // negative decompressed length
exception = assertThrows(LZ4Exception.class, () -> decompressor.decompress(input));
assertEquals("Invalid decompressed length", exception.getMessage());
assertEquals("Invalid decompressed length: -1", exception.getMessage());

int declaredLength = 64 * 1024 * 1024 + 1;
byte[] defaultLimitInput = new byte[4 + (declaredLength + 254) / 255];
Expand All @@ -143,7 +144,7 @@ public void impossibleDeclaredLength(LZ4DecompressorWithLength decompressor) {
defaultLimitInput[2] = (byte) (declaredLength >>> 16);
defaultLimitInput[3] = (byte) (declaredLength >>> 24);
exception = assertThrows(LZ4Exception.class, () -> decompressor.decompress(defaultLimitInput));
assertEquals("Invalid decompressed length", exception.getMessage());
assertEquals("Decompressed length 67108865 exceeds configured maximum 67108864", exception.getMessage());
}

@ParameterizedTest
Expand All @@ -155,21 +156,21 @@ public void destinationBufferLimitsDecompressedLength(LZ4DecompressorWithLength
byte[] destination = new byte[16];
LZ4Exception exception = assertThrows(LZ4Exception.class,
() -> decompressor.decompress(compressed, 0, destination, 1));
assertEquals("Invalid decompressed length", exception.getMessage());
assertEquals("Decompressed length 16 exceeds destination length 15", exception.getMessage());

ByteBuffer movingSrc = ByteBuffer.wrap(compressed);
ByteBuffer movingDest = ByteBuffer.allocate(15);
exception = assertThrows(LZ4Exception.class,
() -> decompressor.decompress(movingSrc, movingDest));
assertEquals("Invalid decompressed length", exception.getMessage());
assertEquals("Decompressed length 16 exceeds destination length 15", exception.getMessage());
assertEquals(0, movingSrc.position());
assertEquals(0, movingDest.position());

ByteBuffer indexedSrc = ByteBuffer.wrap(compressed);
ByteBuffer indexedDest = ByteBuffer.allocate(16);
exception = assertThrows(LZ4Exception.class,
() -> decompressor.decompress(indexedSrc, 0, indexedDest, 1));
assertEquals("Invalid decompressed length", exception.getMessage());
assertEquals("Decompressed length 16 exceeds destination length 15", exception.getMessage());
assertEquals(0, indexedSrc.position());
assertEquals(0, indexedDest.position());
}
Expand All @@ -181,7 +182,7 @@ public void configuredMaximumOnlyLimitsAllocatedOutput(LZ4DecompressorWithLength
byte[] compressed = new LZ4CompressorWithLength(factory.fastCompressor()).compress(new byte[16]);

LZ4Exception exception = assertThrows(LZ4Exception.class, () -> decompressor.decompress(compressed));
assertEquals("Invalid decompressed length", exception.getMessage());
assertEquals("Decompressed length 16 exceeds configured maximum 15", exception.getMessage());

byte[] destination = new byte[16];
decompressor.decompress(compressed, destination);
Expand Down
Loading