You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by bo...@apache.org on 2017/04/25 18:46:03 UTC
[03/10] commons-compress git commit: COMPRESS-382 and COMPRESS-386 --
add memoryLimit to CompressorStreamFactory and ZCompressorInputStream and
LZMACompressorInputStream
COMPRESS-382 and COMPRESS-386 -- add memoryLimit to CompressorStreamFactory
and ZCompressorInputStream and LZMACompressorInputStream
Project: http://git-wip-us.apache.org/repos/asf/commons-compress/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-compress/commit/7d73baf1
Tree: http://git-wip-us.apache.org/repos/asf/commons-compress/tree/7d73baf1
Diff: http://git-wip-us.apache.org/repos/asf/commons-compress/diff/7d73baf1
Branch: refs/heads/master
Commit: 7d73baf10e435fcaa4927495afc185fb473c416b
Parents: 3ecbdd7
Author: tballison <ta...@mitre.org>
Authored: Fri Apr 21 19:21:42 2017 -0400
Committer: tballison <ta...@mitre.org>
Committed: Fri Apr 21 19:21:42 2017 -0400
----------------------------------------------------------------------
.../CompressorMemoryLimitException.java | 36 ++++++++++++++++
.../compressors/CompressorStreamFactory.java | 43 +++++++++++++++++---
.../lzma/LZMACompressorInputStream.java | 6 +--
.../compressors/lzw/LZWInputStream.java | 17 ++++++++
.../compressors/z/ZCompressorInputStream.java | 24 +++++++++--
.../compressors/DetectCompressorTestCase.java | 24 +++++++++++
src/test/resources/COMPRESS-386 | 1 +
7 files changed, 140 insertions(+), 11 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/commons-compress/blob/7d73baf1/src/main/java/org/apache/commons/compress/compressors/CompressorMemoryLimitException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/compress/compressors/CompressorMemoryLimitException.java b/src/main/java/org/apache/commons/compress/compressors/CompressorMemoryLimitException.java
new file mode 100644
index 0000000..4c87d1e
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/compressors/CompressorMemoryLimitException.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.compress.compressors;
+
+/**
+ * If a stream checks for estimated memory allocation, and the estimate
+ * goes above the memory limit, this is thrown.
+ *
+ * @since 1.14
+ */
+public class CompressorMemoryLimitException extends CompressorException {
+
+ public CompressorMemoryLimitException(String message) {
+ super(message);
+ }
+
+ public CompressorMemoryLimitException(String message, Exception e) {
+ super(message, e);
+ }
+}
http://git-wip-us.apache.org/repos/asf/commons-compress/blob/7d73baf1/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java b/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java
index a29179c..a0a816f 100644
--- a/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java
+++ b/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java
@@ -57,6 +57,7 @@ import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.compress.utils.ServiceLoaderIterator;
import org.apache.commons.compress.utils.Sets;
+import org.tukaani.xz.MemoryLimitException;
/**
* <p>
@@ -349,28 +350,52 @@ public class CompressorStreamFactory implements CompressorStreamProvider {
*/
private volatile boolean decompressConcatenated = false;
+ private final int memoryLimitInKb;
/**
* Create an instance with the decompress Concatenated option set to false.
*/
public CompressorStreamFactory() {
this.decompressUntilEOF = null;
+ this.memoryLimitInKb = -1;
}
/**
* Create an instance with the provided decompress Concatenated option.
- *
+ *
* @param decompressUntilEOF
* if true, decompress until the end of the input; if false, stop
* after the first stream and leave the input position to point
* to the next byte after the stream. This setting applies to the
* gzip, bzip2 and xz formats only.
- * @since 1.10
+ *
+ * @param memoryLimitInKb
+ * Some streams require allocation of potentially significant
+ * byte arrays/tables, and they can offer checks to prevent OOMs
+ * on corrupt files. Set the maximum allowed memory allocation in KBs.
+ *
+ * @since 1.14
*/
- public CompressorStreamFactory(final boolean decompressUntilEOF) {
+ public CompressorStreamFactory(final boolean decompressUntilEOF, final int memoryLimitInKb) {
this.decompressUntilEOF = Boolean.valueOf(decompressUntilEOF);
// Also copy to existing variable so can continue to use that as the
// current value
this.decompressConcatenated = decompressUntilEOF;
+ this.memoryLimitInKb = memoryLimitInKb;
+ }
+
+
+ /**
+ * Create an instance with the provided decompress Concatenated option.
+ *
+ * @param decompressUntilEOF
+ * if true, decompress until the end of the input; if false, stop
+ * after the first stream and leave the input position to point
+ * to the next byte after the stream. This setting applies to the
+ * gzip, bzip2 and xz formats only.
+ * @since 1.10
+ */
+ public CompressorStreamFactory(final boolean decompressUntilEOF) {
+ this(decompressUntilEOF, -1);
}
/**
@@ -510,7 +535,11 @@ public class CompressorStreamFactory implements CompressorStreamProvider {
if (!LZMAUtils.isLZMACompressionAvailable()) {
throw new CompressorException("LZMA compression is not available");
}
- return new LZMACompressorInputStream(in);
+ try {
+ return new LZMACompressorInputStream(in, memoryLimitInKb);
+ } catch (MemoryLimitException e) {
+ throw new CompressorMemoryLimitException("exceeded calculated memory limit", e);
+ }
}
if (PACK200.equalsIgnoreCase(name)) {
@@ -526,7 +555,11 @@ public class CompressorStreamFactory implements CompressorStreamProvider {
}
if (Z.equalsIgnoreCase(name)) {
- return new ZCompressorInputStream(in);
+ try {
+ return new ZCompressorInputStream(in, memoryLimitInKb);
+ } catch (ZCompressorInputStream.IOExceptionWrappingMemoryLimitException e) {
+ throw new CompressorMemoryLimitException(e.getMessage());
+ }
}
if (DEFLATE.equalsIgnoreCase(name)) {
http://git-wip-us.apache.org/repos/asf/commons-compress/blob/7d73baf1/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorInputStream.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorInputStream.java b/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorInputStream.java
index ea8a498..0520e62 100644
--- a/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorInputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorInputStream.java
@@ -42,7 +42,7 @@ public class LZMACompressorInputStream extends CompressorInputStream {
*
* @param inputStream where to read the compressed data
*
- * @param memoryLimitKb calculated memory use threshold. Throws MemoryLimitException
+ * @param memoryLimitInKb calculated memory use threshold. Throws MemoryLimitException
* if calculate memory use is above this threshold
*
* @throws IOException if the input is not in the .lzma format,
@@ -53,9 +53,9 @@ public class LZMACompressorInputStream extends CompressorInputStream {
*
* @since 1.14
*/
- public LZMACompressorInputStream(final InputStream inputStream, int memoryLimitKb)
+ public LZMACompressorInputStream(final InputStream inputStream, int memoryLimitInKb)
throws IOException {
- in = new LZMAInputStream(inputStream, memoryLimitKb);
+ in = new LZMAInputStream(inputStream, memoryLimitInKb);
}
/** {@inheritDoc} */
http://git-wip-us.apache.org/repos/asf/commons-compress/blob/7d73baf1/src/main/java/org/apache/commons/compress/compressors/lzw/LZWInputStream.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/compress/compressors/lzw/LZWInputStream.java b/src/main/java/org/apache/commons/compress/compressors/lzw/LZWInputStream.java
index 5967f66..b61d9a1 100644
--- a/src/main/java/org/apache/commons/compress/compressors/lzw/LZWInputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/lzw/LZWInputStream.java
@@ -23,6 +23,7 @@ import java.io.InputStream;
import java.nio.ByteOrder;
import org.apache.commons.compress.compressors.CompressorInputStream;
+import org.apache.commons.compress.compressors.CompressorMemoryLimitException;
import org.apache.commons.compress.utils.BitInputStream;
/**
@@ -114,6 +115,22 @@ public abstract class LZWInputStream extends CompressorInputStream {
/**
* Initializes the arrays based on the maximum code size.
* @param maxCodeSize maximum code size
+ * @param memoryLimitInKb maximum allowed table size in Kb
+ * @throws CompressorMemoryLimitException if maxTableSize is > memoryLimitInKb
+ */
+ protected void initializeTables(final int maxCodeSize, final int memoryLimitInKb)
+ throws CompressorMemoryLimitException {
+ final int maxTableSize = 1 << maxCodeSize;
+ if (memoryLimitInKb > -1 && maxTableSize > memoryLimitInKb*1024) {
+ throw new CompressorMemoryLimitException("Tried to allocate "+maxTableSize +
+ " but memoryLimitInKb only allows "+(memoryLimitInKb*1024));
+ }
+ initializeTables(maxCodeSize);
+ }
+
+ /**
+ * Initializes the arrays based on the maximum code size.
+ * @param maxCodeSize maximum code size
*/
protected void initializeTables(final int maxCodeSize) {
final int maxTableSize = 1 << maxCodeSize;
http://git-wip-us.apache.org/repos/asf/commons-compress/blob/7d73baf1/src/main/java/org/apache/commons/compress/compressors/z/ZCompressorInputStream.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/compress/compressors/z/ZCompressorInputStream.java b/src/main/java/org/apache/commons/compress/compressors/z/ZCompressorInputStream.java
index 799e58d..22d7c0c 100644
--- a/src/main/java/org/apache/commons/compress/compressors/z/ZCompressorInputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/z/ZCompressorInputStream.java
@@ -22,6 +22,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteOrder;
+import org.apache.commons.compress.compressors.CompressorMemoryLimitException;
import org.apache.commons.compress.compressors.lzw.LZWInputStream;
/**
@@ -37,8 +38,9 @@ public class ZCompressorInputStream extends LZWInputStream {
private final boolean blockMode;
private final int maxCodeSize;
private long totalCodesRead = 0;
-
- public ZCompressorInputStream(final InputStream inputStream) throws IOException {
+
+ public ZCompressorInputStream(final InputStream inputStream, int memoryLimitInKb)
+ throws IOException {
super(inputStream, ByteOrder.LITTLE_ENDIAN);
final int firstByte = (int) in.readBits(8);
final int secondByte = (int) in.readBits(8);
@@ -51,9 +53,17 @@ public class ZCompressorInputStream extends LZWInputStream {
if (blockMode) {
setClearCode(DEFAULT_CODE_SIZE);
}
- initializeTables(maxCodeSize);
+ try {
+ initializeTables(maxCodeSize, memoryLimitInKb);
+ } catch (CompressorMemoryLimitException e) {
+ throw new IOExceptionWrappingMemoryLimitException(e.getMessage());
+ }
clearEntries();
}
+
+ public ZCompressorInputStream(final InputStream inputStream) throws IOException {
+ this(inputStream, -1);
+ }
private void clearEntries() {
setTableSize((1 << 8) + (blockMode ? 1 : 0));
@@ -163,4 +173,12 @@ public class ZCompressorInputStream extends LZWInputStream {
return length > 3 && signature[0] == MAGIC_1 && signature[1] == (byte) MAGIC_2;
}
+ /**
+ * Wrapper that subclasses IOException to wrap a MemoryLimitException
+ */
+ public static class IOExceptionWrappingMemoryLimitException extends IOException {
+ public IOExceptionWrappingMemoryLimitException(String message) {
+ super(message);
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/commons-compress/blob/7d73baf1/src/test/java/org/apache/commons/compress/compressors/DetectCompressorTestCase.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/compress/compressors/DetectCompressorTestCase.java b/src/test/java/org/apache/commons/compress/compressors/DetectCompressorTestCase.java
index 67abcd3..7cec5df 100644
--- a/src/test/java/org/apache/commons/compress/compressors/DetectCompressorTestCase.java
+++ b/src/test/java/org/apache/commons/compress/compressors/DetectCompressorTestCase.java
@@ -133,6 +133,10 @@ public final class DetectCompressorTestCase {
assertEquals(CompressorStreamFactory.SNAPPY_FRAMED, detect("bla.tar.sz"));
assertEquals(CompressorStreamFactory.Z, detect("bla.tar.Z"));
+ //make sure we don't oom on detect
+ assertEquals(CompressorStreamFactory.Z, detect("COMPRESS-386"));
+ assertEquals(CompressorStreamFactory.LZMA, detect("COMPRESS-382"));
+
try {
CompressorStreamFactory.detect(new BufferedInputStream(new ByteArrayInputStream(new byte[0])));
fail("shouldn't be able to detect empty stream");
@@ -167,6 +171,26 @@ public final class DetectCompressorTestCase {
}
@Test
+ public void testMemoryLimit() throws Exception {
+ testMemoryLimit("COMPRESS-382");
+ testMemoryLimit("COMPRESS-386");
+ }
+
+ private void testMemoryLimit(String fileName) throws IOException, CompressorException {
+ CompressorStreamFactory fac = new CompressorStreamFactory(true,
+ 100);
+ try (InputStream is = new BufferedInputStream(
+ new FileInputStream(getFile(fileName)))) {
+ InputStream compressorInputStream = fac.createCompressorInputStream(is);
+ fail("Should have thrown CompressorMemoryLimitException");
+ } catch (CompressorMemoryLimitException e) {
+
+ }
+
+ }
+
+
+ @Test
public void testOverride() {
CompressorStreamFactory fac = new CompressorStreamFactory();
assertFalse(fac.getDecompressConcatenated());
http://git-wip-us.apache.org/repos/asf/commons-compress/blob/7d73baf1/src/test/resources/COMPRESS-386
----------------------------------------------------------------------
diff --git a/src/test/resources/COMPRESS-386 b/src/test/resources/COMPRESS-386
new file mode 100644
index 0000000..36d7f52
--- /dev/null
+++ b/src/test/resources/COMPRESS-386
@@ -0,0 +1 @@
+\ufffdB
\ No newline at end of file