Bug 58499: Don't report Zip-Bomb for small files which should not cause memory issues anyway

Also make error message a bit more specific and list classname in Zip-Bomb-Error to make it easier for users what the
problem is and how to find out where the static methods are

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1709180 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Dominik Stadler 2015-10-17 14:40:36 +00:00
parent 43ab1cd35a
commit 3a8f40db7c
4 changed files with 146 additions and 94 deletions

View File

@ -47,6 +47,9 @@ public class ZipSecureFile extends ZipFile {
private static double MIN_INFLATE_RATIO = 0.01d; private static double MIN_INFLATE_RATIO = 0.01d;
private static long MAX_ENTRY_SIZE = 0xFFFFFFFFl; private static long MAX_ENTRY_SIZE = 0xFFFFFFFFl;
// don't alert for expanded sizes smaller than 100k
private static long GRACE_ENTRY_SIZE = 100*1024;
/** /**
* Sets the ratio between de- and inflated bytes to detect zipbomb. * Sets the ratio between de- and inflated bytes to detect zipbomb.
* It defaults to 1% (= 0.01d), i.e. when the compression is better than * It defaults to 1% (= 0.01d), i.e. when the compression is better than
@ -183,16 +186,37 @@ public class ZipSecureFile extends ZipFile {
public void advance(int advance) throws IOException { public void advance(int advance) throws IOException {
counter += advance; counter += advance;
// check the file size first, in case we are working on uncompressed streams // check the file size first, in case we are working on uncompressed streams
if (counter < MAX_ENTRY_SIZE) { if(counter > MAX_ENTRY_SIZE) {
if (cis == null) return; throw new IOException("Zip bomb detected! The file would exceed the max size of the expanded data in the zip-file. "
double ratio = (double)cis.counter/(double)counter; + "This may indicates that the file is used to inflate memory usage and thus could pose a security risk. "
if (ratio >= MIN_INFLATE_RATIO) return; + "You can adjust this limit via ZipSecureFile.setMaxEntrySize() if you need to work with files which are very large. "
+ "Counter: " + counter + ", cis.counter: " + (cis == null ? 0 : cis.counter)
+ "Limits: MAX_ENTRY_SIZE: " + MAX_ENTRY_SIZE);
} }
throw new IOException("Zip bomb detected! The file would exceed certain limits which usually indicate that the file is used to inflate memory usage and thus could pose a security risk. "
+ "You can adjust these limits via setMinInflateRatio() and setMaxEntrySize() if you need to work with files which exceed these limits. " // no expanded size?
if (cis == null) {
return;
}
// don't alert for small expanded size
if (counter <= GRACE_ENTRY_SIZE) {
return;
}
double ratio = (double)cis.counter/(double)counter;
if (ratio >= MIN_INFLATE_RATIO) {
return;
}
// one of the limits was reached, report it
throw new IOException("Zip bomb detected! The file would exceed the max. ratio of compressed file size to the size of the expanded data. "
+ "This may indicate that the file is used to inflate memory usage and thus could pose a security risk. "
+ "You can adjust this limit via ZipSecureFile.setMinInflateRatio() if you need to work with files which exceed this limit. "
+ "Counter: " + counter + ", cis.counter: " + (cis == null ? 0 : cis.counter) + ", ratio: " + (cis == null ? 0 : ((double)cis.counter)/counter) + "Counter: " + counter + ", cis.counter: " + (cis == null ? 0 : cis.counter) + ", ratio: " + (cis == null ? 0 : ((double)cis.counter)/counter)
+ "Limits: MIN_INFLATE_RATIO: " + MIN_INFLATE_RATIO + ", MAX_ENTRY_SIZE: " + MAX_ENTRY_SIZE); + "Limits: MIN_INFLATE_RATIO: " + MIN_INFLATE_RATIO);
} }
public ZipEntry getNextEntry() throws IOException { public ZipEntry getNextEntry() throws IOException {

View File

@ -28,7 +28,6 @@ import static org.junit.Assert.fail;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import org.apache.poi.ss.usermodel.BaseTestWorkbook; import org.apache.poi.ss.usermodel.BaseTestWorkbook;
@ -209,7 +208,7 @@ public final class TestSXSSFWorkbook extends BaseTestWorkbook {
} }
@Test @Test
public void sheetdataWriter(){ public void sheetdataWriter() throws IOException{
SXSSFWorkbook wb = new SXSSFWorkbook(); SXSSFWorkbook wb = new SXSSFWorkbook();
SXSSFSheet sh = wb.createSheet(); SXSSFSheet sh = wb.createSheet();
SheetDataWriter wr = sh.getSheetDataWriter(); SheetDataWriter wr = sh.getSheetDataWriter();
@ -218,6 +217,7 @@ public final class TestSXSSFWorkbook extends BaseTestWorkbook {
assertTrue(tmp.getName().startsWith("poi-sxssf-sheet")); assertTrue(tmp.getName().startsWith("poi-sxssf-sheet"));
assertTrue(tmp.getName().endsWith(".xml")); assertTrue(tmp.getName().endsWith(".xml"));
assertTrue(wb.dispose()); assertTrue(wb.dispose());
wb.close();
wb = new SXSSFWorkbook(); wb = new SXSSFWorkbook();
wb.setCompressTempFiles(true); wb.setCompressTempFiles(true);
@ -228,6 +228,7 @@ public final class TestSXSSFWorkbook extends BaseTestWorkbook {
assertTrue(tmp.getName().startsWith("poi-sxssf-sheet-xml")); assertTrue(tmp.getName().startsWith("poi-sxssf-sheet-xml"));
assertTrue(tmp.getName().endsWith(".gz")); assertTrue(tmp.getName().endsWith(".gz"));
assertTrue(wb.dispose()); assertTrue(wb.dispose());
wb.close();
//Test escaping of Unicode control characters //Test escaping of Unicode control characters
wb = new SXSSFWorkbook(); wb = new SXSSFWorkbook();
@ -237,6 +238,7 @@ public final class TestSXSSFWorkbook extends BaseTestWorkbook {
assertEquals("value?", cell.getStringCellValue()); assertEquals("value?", cell.getStringCellValue());
assertTrue(wb.dispose()); assertTrue(wb.dispose());
wb.close();
} }
@ -329,80 +331,74 @@ public final class TestSXSSFWorkbook extends BaseTestWorkbook {
// currently writing the same sheet multiple times is not supported... // currently writing the same sheet multiple times is not supported...
@Ignore @Ignore
public void bug53515() throws Exception { public void bug53515() throws Exception {
Workbook wb = new SXSSFWorkbook(10); Workbook wb = new SXSSFWorkbook(10);
populateWorkbook(wb); populateWorkbook(wb);
saveTwice(wb); saveTwice(wb);
wb = new XSSFWorkbook(); wb = new XSSFWorkbook();
populateWorkbook(wb); populateWorkbook(wb);
saveTwice(wb); saveTwice(wb);
} }
// Crashes the JVM because of documented JVM behavior with concurrent writing/reading of zip-files // Crashes the JVM because of documented JVM behavior with concurrent writing/reading of zip-files
// See http://www.oracle.com/technetwork/java/javase/documentation/overview-156328.html // See http://www.oracle.com/technetwork/java/javase/documentation/overview-156328.html
@Ignore @Ignore
public void bug53515a() throws Exception { public void bug53515a() throws Exception {
File out = new File("Test.xlsx"); File out = new File("Test.xlsx");
out.delete(); out.delete();
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
System.out.println("Iteration " + i); System.out.println("Iteration " + i);
final SXSSFWorkbook wb; final SXSSFWorkbook wb;
if (out.exists()) { if (out.exists()) {
wb = new SXSSFWorkbook( wb = new SXSSFWorkbook(
(XSSFWorkbook) WorkbookFactory.create(out)); (XSSFWorkbook) WorkbookFactory.create(out));
} else { } else {
wb = new SXSSFWorkbook(10); wb = new SXSSFWorkbook(10);
} }
try { try {
FileOutputStream outSteam = new FileOutputStream(out); FileOutputStream outSteam = new FileOutputStream(out);
if (i == 0) { if (i == 0) {
populateWorkbook(wb); populateWorkbook(wb);
} else { } else {
System.gc(); System.gc();
System.gc(); System.gc();
System.gc(); System.gc();
} }
wb.write(outSteam); wb.write(outSteam);
// assertTrue(wb.dispose()); // assertTrue(wb.dispose());
outSteam.close(); outSteam.close();
} finally { } finally {
assertTrue(wb.dispose()); assertTrue(wb.dispose());
} }
} }
out.delete(); out.delete();
} }
private static void populateWorkbook(Workbook wb) { private static void populateWorkbook(Workbook wb) {
Sheet sh = wb.createSheet(); Sheet sh = wb.createSheet();
for (int rownum = 0; rownum < 100; rownum++) { for (int rownum = 0; rownum < 100; rownum++) {
Row row = sh.createRow(rownum); Row row = sh.createRow(rownum);
for (int cellnum = 0; cellnum < 10; cellnum++) { for (int cellnum = 0; cellnum < 10; cellnum++) {
Cell cell = row.createCell(cellnum); Cell cell = row.createCell(cellnum);
String address = new CellReference(cell).formatAsString(); String address = new CellReference(cell).formatAsString();
cell.setCellValue(address); cell.setCellValue(address);
} }
} }
} }
private static void saveTwice(Workbook wb) throws Exception { private static void saveTwice(Workbook wb) throws Exception {
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
try { try {
NullOutputStream out = new NullOutputStream(); NullOutputStream out = new NullOutputStream();
wb.write(out); wb.write(out);
out.close(); out.close();
} catch (Exception e) { } catch (Exception e) {
throw new Exception("ERROR: failed on " + (i + 1) throw new Exception("ERROR: failed on " + (i + 1)
+ "th time calling " + wb.getClass().getName() + "th time calling " + wb.getClass().getName()
+ ".write() with exception " + e.getMessage(), e); + ".write() with exception " + e.getMessage(), e);
} }
} }
} }
private static class NullOutputStream extends OutputStream {
@Override
public void write(int b) throws IOException {
}
}
} }

View File

@ -81,19 +81,6 @@ public final class TestHSSFWorkbook extends BaseTestWorkbook {
return wb.getWorkbook(); return wb.getWorkbook();
} }
@Test
public void windowOneDefaults() throws IOException {
HSSFWorkbook b = new HSSFWorkbook( );
try {
assertEquals(b.getActiveSheetIndex(), 0);
assertEquals(b.getFirstVisibleTab(), 0);
} catch (NullPointerException npe) {
fail("WindowOneRecord in Workbook is probably not initialized");
}
b.close();
}
/** /**
* Tests for {@link HSSFWorkbook#isHidden()} etc * Tests for {@link HSSFWorkbook#isHidden()} etc
* @throws IOException * @throws IOException

View File

@ -26,15 +26,16 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.util.ConcurrentModificationException; import java.util.ConcurrentModificationException;
import java.util.Iterator; import java.util.Iterator;
import junit.framework.AssertionFailedError;
import org.apache.poi.ss.ITestDataProvider; import org.apache.poi.ss.ITestDataProvider;
import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.CellRangeAddress;
import org.junit.Test; import org.junit.Test;
import junit.framework.AssertionFailedError;
/** /**
* @author Yegor Kozlov * @author Yegor Kozlov
*/ */
@ -753,4 +754,48 @@ public abstract class BaseTestWorkbook {
sheets[i], wb.getSheetAt(i).getSheetName()); sheets[i], wb.getSheetAt(i).getSheetName());
} }
} }
protected static class NullOutputStream extends OutputStream {
public NullOutputStream() {
}
@Override
public void write(int b) throws IOException {
}
}
@Test
public void test58499() throws IOException {
Workbook workbook = _testDataProvider.createWorkbook();
Sheet sheet = workbook.createSheet();
for (int i = 0; i < 900; i++) {
Row r = sheet.createRow(i);
Cell c = r.createCell(0);
CellStyle cs = workbook.createCellStyle();
c.setCellStyle(cs);
c.setCellValue("AAA");
}
OutputStream os = new NullOutputStream();
try {
workbook.write(os);
} finally {
os.close();
}
//workbook.dispose();
workbook.close();
}
@Test
public void windowOneDefaults() throws IOException {
Workbook b = _testDataProvider.createWorkbook();
try {
assertEquals(b.getActiveSheetIndex(), 0);
assertEquals(b.getFirstVisibleTab(), 0);
} catch (NullPointerException npe) {
fail("WindowOneRecord in Workbook is probably not initialized");
}
b.close();
}
} }