diff --git a/src/main/java/org/codehaus/plexus/archiver/AbstractUnArchiver.java b/src/main/java/org/codehaus/plexus/archiver/AbstractUnArchiver.java index 58e121bbd..31315b431 100644 --- a/src/main/java/org/codehaus/plexus/archiver/AbstractUnArchiver.java +++ b/src/main/java/org/codehaus/plexus/archiver/AbstractUnArchiver.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import org.apache.commons.compress.utils.BoundedInputStream; import org.codehaus.plexus.archiver.util.ArchiveEntryUtils; import org.codehaus.plexus.components.io.attributes.SymlinkUtils; import org.codehaus.plexus.components.io.filemappers.FileMapper; @@ -316,9 +317,17 @@ public void setIgnorePermissions( final boolean ignorePermissions ) this.ignorePermissions = ignorePermissions; } - protected void extractFile( final File srcF, final File dir, final InputStream compressedInputStream, - String entryName, final Date entryDate, final boolean isDirectory, - final Integer mode, String symlinkDestination, final FileMapper[] fileMappers ) + protected File extractFile(final File srcF, final File dir, final InputStream compressedInputStream, + String entryName, final Date entryDate, final boolean isDirectory, + final Integer mode, String symlinkDestination, final FileMapper[] fileMappers) + throws IOException, ArchiverException { + return extractFile(srcF, dir, compressedInputStream, Long.MAX_VALUE, entryName, entryDate, isDirectory, + mode, symlinkDestination, fileMappers); + } + + protected File extractFile(final File srcF, final File dir, final InputStream compressedInputStream, + Long remainingSpace, String entryName, final Date entryDate, final boolean isDirectory, + final Integer mode, String symlinkDestination, final FileMapper[] fileMappers ) throws IOException, ArchiverException { if ( fileMappers != null ) @@ -345,7 +354,7 @@ protected void extractFile( final File srcF, final File dir, final InputStream c { if ( !isOverwrite() && f.exists() && ( f.lastModified() >= entryDate.getTime() ) ) { - return; + return f; } // create intermediary directories - sometimes zip don't add them @@ -365,18 +374,8 @@ else if ( isDirectory ) } else { - OutputStream out = null; - try - { - out = new FileOutputStream( f ); - - IOUtil.copy( compressedInputStream, out ); - out.close(); - out = null; - } - finally - { - IOUtil.close( out ); + try (OutputStream out = new FileOutputStream(f)) { + IOUtil.copy(new BoundedInputStream(compressedInputStream, remainingSpace), out); } } @@ -386,10 +385,13 @@ else if ( isDirectory ) { ArchiveEntryUtils.chmod( f, mode ); } + + return f; } catch ( final FileNotFoundException ex ) { getLogger().warn( "Unable to expand to file " + f.getPath() ); + return null; } } 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 4e185e270..1f96ea8a9 100644 --- a/src/main/java/org/codehaus/plexus/archiver/zip/AbstractZipUnArchiver.java +++ b/src/main/java/org/codehaus/plexus/archiver/zip/AbstractZipUnArchiver.java @@ -24,13 +24,12 @@ import java.util.Date; import java.util.Enumeration; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.apache.commons.compress.archivers.zip.UnicodePathExtraField; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipFile; -import org.apache.commons.compress.utils.IOUtils; import org.codehaus.plexus.archiver.AbstractUnArchiver; import org.codehaus.plexus.archiver.ArchiverException; -import org.codehaus.plexus.components.io.filemappers.FileMapper; import org.codehaus.plexus.components.io.resources.PlexusIoResource; /** @@ -44,6 +43,9 @@ public abstract class AbstractZipUnArchiver private String encoding = "UTF8"; + @Nullable + private Long maxOutputSize; + public AbstractZipUnArchiver() { } @@ -68,6 +70,14 @@ public void setEncoding( String encoding ) this.encoding = encoding; } + /** + * Set produced output size limit as a way of protection against zip bombs + * @param maxOutputSize - max size of produced output in bytes + */ + public void setMaxOutputSize(Long maxOutputSize) { + this.maxOutputSize = maxOutputSize; + } + private static class ZipEntryFileInfo implements PlexusIoResource { @@ -160,78 +170,58 @@ public boolean isExisting() protected void execute() throws ArchiverException { - getLogger().debug( "Expanding: " + getSourceFile() + " into " + getDestDirectory() ); - org.apache.commons.compress.archivers.zip.ZipFile zf = null; - InputStream in = null; - try + try(ZipFile zipFile = new ZipFile( getSourceFile(), encoding, true )) { - zf = new org.apache.commons.compress.archivers.zip.ZipFile( getSourceFile(), encoding, true ); - final Enumeration e = zf.getEntriesInPhysicalOrder(); + getLogger().debug( "Expanding: " + getSourceFile() + " into " + getDestDirectory() ); + Long remainingSpace = maxOutputSize; + final Enumeration e = zipFile.getEntriesInPhysicalOrder(); while ( e.hasMoreElements() ) { final ZipArchiveEntry ze = (ZipArchiveEntry) e.nextElement(); - final ZipEntryFileInfo fileInfo = new ZipEntryFileInfo( zf, ze ); + final ZipEntryFileInfo fileInfo = new ZipEntryFileInfo( zipFile, ze ); if ( isSelected( fileInfo.getName(), fileInfo ) ) { - in = zf.getInputStream( ze ); - - extractFileIfIncluded( getSourceFile(), getDestDirectory(), in, fileInfo.getName(), - new Date( ze.getTime() ), ze.isDirectory(), - ze.getUnixMode() != 0 ? ze.getUnixMode() : null, - resolveSymlink( zf, ze ), getFileMappers() ); - - in.close(); - in = null; + try (InputStream in = zipFile.getInputStream(ze)) { + if (remainingSpace != null) + { + File file = extractFile(getSourceFile(), getDestDirectory(), in, remainingSpace, + fileInfo.getName(), new Date(ze.getTime()), ze.isDirectory(), + ze.getUnixMode() != 0 ? ze.getUnixMode() : null, + resolveSymlink(zipFile, ze), getFileMappers()); + if (file != null) + { + remainingSpace -= file.length(); + if (remainingSpace <= 0) + { + throw new ArchiverException("Maximum output size limit reached"); + } + } + } + else + { + extractFile(getSourceFile(), getDestDirectory(), in, fileInfo.getName(), + new Date(ze.getTime()), ze.isDirectory(), + ze.getUnixMode() != 0 ? ze.getUnixMode() : null, + resolveSymlink(zipFile, ze), getFileMappers()); + } + } } } - - zf.close(); - zf = null; - getLogger().debug( "expand complete" ); } catch ( final IOException ioe ) { throw new ArchiverException( "Error while expanding " + getSourceFile().getAbsolutePath(), ioe ); } - finally - { - IOUtils.closeQuietly( in ); - IOUtils.closeQuietly( zf ); - } - } - - private String resolveSymlink( ZipFile zf, ZipArchiveEntry ze ) - throws IOException - { - if ( ze.isUnixSymlink() ) - { - return zf.getUnixSymlink( ze ); - } - else - { - return null; - } - } - - private void extractFileIfIncluded( final File sourceFile, final File destDirectory, final InputStream inputStream, - final String name, final Date time, final boolean isDirectory, - final Integer mode, String symlinkDestination, final FileMapper[] fileMappers ) - throws IOException, ArchiverException - { - extractFile( sourceFile, destDirectory, inputStream, name, time, isDirectory, mode, symlinkDestination, fileMappers ); } @Override protected void execute( final String path, final File outputDirectory ) throws ArchiverException { - org.apache.commons.compress.archivers.zip.ZipFile zipFile = null; - InputStream in = null; - try + try(ZipFile zipFile = new ZipFile( getSourceFile(), encoding, true )) { - zipFile = new org.apache.commons.compress.archivers.zip.ZipFile( getSourceFile(), encoding, true ); - + Long remainingSpace = maxOutputSize; final Enumeration e = zipFile.getEntriesInPhysicalOrder(); while ( e.hasMoreElements() ) @@ -245,30 +235,49 @@ protected void execute( final String path, final File outputDirectory ) if ( ze.getName().startsWith( path ) ) { - in = zipFile.getInputStream( ze ); - - extractFileIfIncluded( getSourceFile(), outputDirectory, in, - ze.getName(), new Date( ze.getTime() ), ze.isDirectory(), - ze.getUnixMode() != 0 ? ze.getUnixMode() : null, - resolveSymlink( zipFile, ze ), getFileMappers() ); - - in.close(); - in = null; + try(InputStream in = zipFile.getInputStream( ze )) { + if (remainingSpace != null) + { + File file = extractFile(getSourceFile(), outputDirectory, in, remainingSpace, + ze.getName(), new Date(ze.getTime()), ze.isDirectory(), + ze.getUnixMode() != 0 ? ze.getUnixMode() : null, + resolveSymlink(zipFile, ze), getFileMappers()); + if (file != null) + { + remainingSpace -= file.length(); + if (remainingSpace <= 0) + { + throw new ArchiverException("Maximum output size limit reached"); + } + } + } + else + { + extractFile(getSourceFile(), outputDirectory, in, + ze.getName(), new Date(ze.getTime()), ze.isDirectory(), + ze.getUnixMode() != 0 ? ze.getUnixMode() : null, + resolveSymlink(zipFile, ze), getFileMappers()); + } + } } } - - zipFile.close(); - zipFile = null; } catch ( final IOException ioe ) { throw new ArchiverException( "Error while expanding " + getSourceFile().getAbsolutePath(), ioe ); } - finally + } + + private String resolveSymlink( ZipFile zf, ZipArchiveEntry ze ) + throws IOException + { + if ( ze.isUnixSymlink() ) + { + return zf.getUnixSymlink( ze ); + } + else { - IOUtils.closeQuietly( in ); - IOUtils.closeQuietly( zipFile ); + return null; } } - } 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