fix: prevent zipbomb forged headers attacks (#980, PR #982)

This commit is contained in:
skylot
2020-09-27 21:10:30 +03:00
committed by GitHub
parent 73ca2e0fa4
commit 9b1761f71f
6 changed files with 125 additions and 40 deletions
@@ -0,0 +1,53 @@
package jadx.api.plugins.utils;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public class LimitedInputStream extends FilterInputStream {
private final long maxSize;
private long currentPos;
protected LimitedInputStream(InputStream in, long maxSize) {
super(in);
this.maxSize = maxSize;
}
private void checkPos() {
if (currentPos > maxSize) {
throw new IllegalStateException("Read limit exceeded");
}
}
@Override
public int read() throws IOException {
int data = super.read();
if (data != -1) {
currentPos++;
checkPos();
}
return data;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int count = super.read(b, off, len);
if (count > 0) {
currentPos += count;
checkPos();
}
return count;
}
@Override
public long skip(long n) throws IOException {
long skipped = super.skip(n);
if (skipped != 0) {
currentPos += skipped;
checkPos();
}
return skipped;
}
}
@@ -1,8 +1,13 @@
package jadx.api.plugins.utils;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.function.BiConsumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -12,6 +17,7 @@ public class ZipSecurity {
// size of uncompressed zip entry shouldn't be bigger of compressed in MAX_SIZE_DIFF times
private static final int MAX_SIZE_DIFF = 100;
private static final int MAX_ENTRIES_COUNT = 100_000;
private ZipSecurity() {
}
@@ -73,4 +79,40 @@ public class ZipSecurity {
return isValidZipEntryName(entry.getName())
&& !isZipBomb(entry);
}
public static InputStream getInputStreamForEntry(ZipFile zipFile, ZipEntry entry) throws IOException {
InputStream in = zipFile.getInputStream(entry);
LimitedInputStream limited = new LimitedInputStream(in, entry.getSize());
return new BufferedInputStream(limited);
}
public static void visitZipEntries(File file, BiConsumer<ZipFile, ZipEntry> visitor) {
try (ZipFile zip = new ZipFile(file)) {
Enumeration<? extends ZipEntry> entries = zip.entries();
int entriesProcessed = 0;
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (!entry.isDirectory() && isValidZipEntry(entry)) {
visitor.accept(zip, entry);
entriesProcessed++;
if (entriesProcessed > MAX_ENTRIES_COUNT) {
throw new IllegalStateException("Zip entries count limit exceeded: " + MAX_ENTRIES_COUNT
+ ", last entry: " + entry.getName());
}
}
}
} catch (Exception e) {
throw new RuntimeException("Failed to process zip file: " + file.getAbsolutePath(), e);
}
}
public static void readZipEntries(File file, BiConsumer<ZipEntry, InputStream> visitor) {
visitZipEntries(file, (zip, entry) -> {
try (InputStream in = getInputStreamForEntry(zip, entry)) {
visitor.accept(entry, in);
} catch (Exception e) {
throw new RuntimeException("Error process zip entry: " + entry.getName());
}
});
}
}