diff --git a/src/main/java/org/codehaus/plexus/archiver/zip/AbstractZipUnArchiver.java b/src/main/java/org/codehaus/plexus/archiver/zip/AbstractZipUnArchiver.java index 7543f8981..6319bb755 100644 --- a/src/main/java/org/codehaus/plexus/archiver/zip/AbstractZipUnArchiver.java +++ b/src/main/java/org/codehaus/plexus/archiver/zip/AbstractZipUnArchiver.java @@ -29,6 +29,8 @@ import org.apache.commons.compress.archivers.zip.ZipFile; import org.codehaus.plexus.archiver.AbstractUnArchiver; import org.codehaus.plexus.archiver.ArchiverException; +import org.apache.commons.io.input.BoundedInputStream; +import org.apache.commons.io.input.CountingInputStream; import org.codehaus.plexus.components.io.resources.PlexusIoResource; /** @@ -42,6 +44,8 @@ public abstract class AbstractZipUnArchiver private String encoding = "UTF8"; + private long maxOutputSize = Long.MAX_VALUE; + public AbstractZipUnArchiver() { } @@ -66,6 +70,21 @@ public void setEncoding( String encoding ) this.encoding = encoding; } + /** + * Set maximum allowed size of the produced output. + * + * It may be used as a protection against zip bombs. + * + * @param maxOutputSize max size of the produced output, in bytes. Must be greater than 0 + * @throws IllegalArgumentException if specified output size is less or equal to 0 + */ + public void setMaxOutputSize( long maxOutputSize ) { + if ( maxOutputSize <= 0 ) { + throw new IllegalArgumentException( "Invalid max output size specified: " + maxOutputSize ); + } + this.maxOutputSize = maxOutputSize; + } + private static class ZipEntryFileInfo implements PlexusIoResource { @@ -181,6 +200,7 @@ protected void execute( final String path, final File outputDirectory ) getLogger().debug( "Expanding: " + getSourceFile() + " into " + outputDirectory ); try ( ZipFile zipFile = new ZipFile( getSourceFile(), encoding, true ) ) { + long remainingSpace = maxOutputSize; final Enumeration e = zipFile.getEntriesInPhysicalOrder(); while ( e.hasMoreElements() ) @@ -196,10 +216,18 @@ protected void execute( final String path, final File outputDirectory ) { try ( InputStream in = zipFile.getInputStream( ze ) ) { - extractFile( getSourceFile(), outputDirectory, in, - ze.getName(), new Date( ze.getTime() ), ze.isDirectory(), - ze.getUnixMode() != 0 ? ze.getUnixMode() : null, - resolveSymlink( zipFile, ze ), getFileMappers() ); + BoundedInputStream bis = new BoundedInputStream( in, remainingSpace + 1 ); + CountingInputStream cis = new CountingInputStream( bis ); + extractFile( getSourceFile(), outputDirectory, cis, + ze.getName(), new Date( ze.getTime() ), ze.isDirectory(), + ze.getUnixMode() != 0 ? ze.getUnixMode() : null, + resolveSymlink( zipFile, ze ), getFileMappers() ); + + remainingSpace -= cis.getByteCount(); + if ( remainingSpace < 0 ) + { + throw new ArchiverException("Maximum output size limit reached"); + } } } } diff --git a/src/test/java/org/codehaus/plexus/archiver/zip/ZipUnArchiverTest.java b/src/test/java/org/codehaus/plexus/archiver/zip/ZipUnArchiverTest.java index ea46a5aae..fbb95371d 100644 --- a/src/test/java/org/codehaus/plexus/archiver/zip/ZipUnArchiverTest.java +++ b/src/test/java/org/codehaus/plexus/archiver/zip/ZipUnArchiverTest.java @@ -214,6 +214,31 @@ public void testExtractingZipWithEntryOutsideDestDirThrowsException() assertTrue( ex.getMessage().startsWith( "Entry is outside of the target directory" ) ); } + public void testZipOutputSizeException() + throws Exception + { + Exception ex = null; + String s = "target/zip-size-tests"; + File testZip = new File( getBasedir(), "src/test/jars/test.zip" ); + File outputDirectory = new File( getBasedir(), s ); + + FileUtils.deleteDirectory( outputDirectory ); + + try + { + ZipUnArchiver zu = getZipUnArchiver( testZip ); + zu.setMaxOutputSize(10L); + zu.extract( "", outputDirectory ); + } + catch ( Exception e ) + { + ex = e; + } + + assertNotNull( ex ); + assertTrue( ex.getMessage().startsWith( "Maximum output size limit reached" ) ); + } + private ZipArchiver getZipArchiver() { try