Skip to content

Commit

Permalink
Add ability to limit output size for zip unarchiver as a way of prote…
Browse files Browse the repository at this point in the history
…ction against zip bombs.
  • Loading branch information
Sergey Patrikeev authored and Sergey Patrikeev committed Aug 23, 2019
1 parent 40f072b commit bb22cd4
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 4 deletions.
Expand Up @@ -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;

/**
Expand All @@ -42,6 +44,8 @@ public abstract class AbstractZipUnArchiver

private String encoding = "UTF8";

private long maxOutputSize = Long.MAX_VALUE;

public AbstractZipUnArchiver()
{
}
Expand All @@ -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 <a href="https://en.wikipedia.org/wiki/Zip_bomb">zip bombs</a>.
*
* @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
{
Expand Down Expand Up @@ -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<ZipArchiveEntry> e = zipFile.getEntriesInPhysicalOrder();

while ( e.hasMoreElements() )
Expand All @@ -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");
}
}
}
}
Expand Down
Expand Up @@ -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
Expand Down

0 comments on commit bb22cd4

Please sign in to comment.