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
12 changes: 9 additions & 3 deletions src/java/net/jpountz/lz4/LZ4BlockInputStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@
* <p>Use {@link Builder#withAcceptOversizedBlocks(boolean)} only for trusted
* inputs that may contain noncanonical legacy LZ4 blocks. Enabling it restores
* acceptance of blocks whose compressed length is greater than or equal to the
* original length, which may permit large attacker-controlled allocations.</p>
* original length, which may permit large attacker-controlled allocations.
* Even then, blocks whose compressed length exceeds the LZ4 compress bound of
* their original length are rejected.</p>
* @see LZ4BlockOutputStream
*/
public class LZ4BlockInputStream extends FilterInputStream {
Expand Down Expand Up @@ -310,7 +312,9 @@ private boolean readBlock() throws IOException {
|| (originalLen == 0 && compressedLen != 0)
|| (originalLen != 0 && compressedLen == 0)
|| (compressionMethod == COMPRESSION_METHOD_RAW && originalLen != compressedLen)
|| (compressionMethod == COMPRESSION_METHOD_LZ4 && !acceptOversizedBlocks && compressedLen >= originalLen)) {
|| (compressionMethod == COMPRESSION_METHOD_LZ4 && !acceptOversizedBlocks && compressedLen >= originalLen)
// even an oversized LZ4 block cannot be larger than the LZ4 compress bound of its original length
|| (compressionMethod == COMPRESSION_METHOD_LZ4 && compressedLen > LZ4Utils.maxCompressedLength(originalLen))) {
throw new IOException("Stream is corrupted");
}
if (originalLen == 0 && compressedLen == 0) {
Expand Down Expand Up @@ -436,7 +440,9 @@ public Builder withStopOnEmptyBlock(boolean stopOnEmptyBlock) {
* Registers whether the builder should accept oversized blocks whose
* compressed length is greater than or equal to the original length.
* <p>Only enable this for trusted inputs. Accepting such blocks may permit
* large attacker-controlled allocations.</p>
* large attacker-controlled allocations. Blocks whose compressed length
* exceeds the LZ4 compress bound of their original length are rejected
* even when this is enabled.</p>
*
* @param acceptOversizedBlocks whether oversized blocks should be accepted
* @return current builder instance
Expand Down
20 changes: 20 additions & 0 deletions src/test/net/jpountz/lz4/LZ4BlockStreamingTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,26 @@ public void testAcceptsInvalidLz4CompressedLengthInOversizedBlocksMode() throws
in.close();
}

@Test
public void testRejectsLz4CompressedLengthAboveCompressBoundInOversizedBlocksMode() {
final int originalLen = 16;
for (int compressedLen : new int[] {LZ4Utils.maxCompressedLength(originalLen) + 1, Integer.MAX_VALUE}) {
// header only: the block is rejected before its payload or checksum is read
final byte[] header = ByteBuffer.allocate(LZ4BlockOutputStream.HEADER_LENGTH)
.order(ByteOrder.LITTLE_ENDIAN)
.put(LZ4BlockOutputStream.MAGIC)
.put((byte) (LZ4BlockOutputStream.COMPRESSION_METHOD_LZ4 | 0x0F))
.putInt(compressedLen)
.putInt(originalLen)
.array();
final LZ4BlockInputStream in = lz4BlockInputStreamBuilder()
.withAcceptOversizedBlocks(true)
.build(new ByteArrayInputStream(header));
final IOException e = assertThrows(IOException.class, in::read);
assertEquals("Stream is corrupted", e.getMessage());
}
}

// Many empty blocks followed by a regular stream, as could be produced by a malicious peer
private static byte[] emptyBlocksFollowedBy(int emptyBlocks, byte[] tail) {
final byte[] emptyBlock = new byte[LZ4BlockOutputStream.HEADER_LENGTH];
Expand Down
Loading