Skip to content

8193682: Infinite loop in ZipOutputStream.close() #558

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions jdk/src/share/classes/java/util/zip/DeflaterOutputStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,12 @@ public void finish() throws IOException {
*/
public void close() throws IOException {
if (!closed) {
finish();
if (usesDefaultDeflater)
def.end();
try {
finish();
} finally {
if (usesDefaultDeflater)
def.end();
}
out.close();
closed = true;
}
Expand Down
38 changes: 22 additions & 16 deletions jdk/src/share/classes/java/util/zip/GZIPOutputStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -154,24 +154,30 @@ public synchronized void write(byte[] buf, int off, int len)
*/
public void finish() throws IOException {
if (!def.finished()) {
def.finish();
while (!def.finished()) {
int len = def.deflate(buf, 0, buf.length);
if (def.finished() && len <= buf.length - TRAILER_SIZE) {
// last deflater buffer. Fit trailer at the end
writeTrailer(buf, len);
len = len + TRAILER_SIZE;
out.write(buf, 0, len);
return;
try {
def.finish();
while (!def.finished()) {
int len = def.deflate(buf, 0, buf.length);
if (def.finished() && len <= buf.length - TRAILER_SIZE) {
// last deflater buffer. Fit trailer at the end
writeTrailer(buf, len);
len = len + TRAILER_SIZE;
out.write(buf, 0, len);
return;
}
if (len > 0)
out.write(buf, 0, len);
}
if (len > 0)
out.write(buf, 0, len);
// if we can't fit the trailer at the end of the last
// deflater buffer, we write it separately
byte[] trailer = new byte[TRAILER_SIZE];
writeTrailer(trailer, 0);
out.write(trailer);
} catch (IOException e) {
if (usesDefaultDeflater)
def.end();
throw e;
}
// if we can't fit the trailer at the end of the last
// deflater buffer, we write it separately
byte[] trailer = new byte[TRAILER_SIZE];
writeTrailer(trailer, 0);
out.write(trailer);
}
}

Expand Down
106 changes: 57 additions & 49 deletions jdk/src/share/classes/java/util/zip/ZipOutputStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -247,59 +247,67 @@ public void putNextEntry(ZipEntry e) throws IOException {
public void closeEntry() throws IOException {
ensureOpen();
if (current != null) {
ZipEntry e = current.entry;
switch (e.method) {
case DEFLATED:
def.finish();
while (!def.finished()) {
deflate();
}
if ((e.flag & 8) == 0) {
// verify size, compressed size, and crc-32 settings
if (e.size != def.getBytesRead()) {
throw new ZipException(
"invalid entry size (expected " + e.size +
" but got " + def.getBytesRead() + " bytes)");
}
if (e.csize != def.getBytesWritten()) {
throw new ZipException(
"invalid entry compressed size (expected " +
e.csize + " but got " + def.getBytesWritten() + " bytes)");
try {
ZipEntry e = current.entry;
switch (e.method) {
case DEFLATED: {
def.finish();
while (!def.finished()) {
deflate();
}
if ((e.flag & 8) == 0) {
// verify size, compressed size, and crc-32 settings
if (e.size != def.getBytesRead()) {
throw new ZipException(
"invalid entry size (expected " + e.size +
" but got " + def.getBytesRead() + " bytes)");
}
if (e.csize != def.getBytesWritten()) {
throw new ZipException(
"invalid entry compressed size (expected " +
e.csize + " but got " + def.getBytesWritten() + " bytes)");
}
if (e.crc != crc.getValue()) {
throw new ZipException(
"invalid entry CRC-32 (expected 0x" +
Long.toHexString(e.crc) + " but got 0x" +
Long.toHexString(crc.getValue()) + ")");
}
} else {
e.size = def.getBytesRead();
e.csize = def.getBytesWritten();
e.crc = crc.getValue();
writeEXT(e);
}
def.reset();
written += e.csize;
}
if (e.crc != crc.getValue()) {
throw new ZipException(
"invalid entry CRC-32 (expected 0x" +
Long.toHexString(e.crc) + " but got 0x" +
Long.toHexString(crc.getValue()) + ")");
break;
case STORED: {
// we already know that both e.size and e.csize are the same
if (e.size != written - locoff) {
throw new ZipException(
"invalid entry size (expected " + e.size +
" but got " + (written - locoff) + " bytes)");
}
if (e.crc != crc.getValue()) {
throw new ZipException(
"invalid entry crc-32 (expected 0x" +
Long.toHexString(e.crc) + " but got 0x" +
Long.toHexString(crc.getValue()) + ")");
}
}
} else {
e.size = def.getBytesRead();
e.csize = def.getBytesWritten();
e.crc = crc.getValue();
writeEXT(e);
}
def.reset();
written += e.csize;
break;
case STORED:
// we already know that both e.size and e.csize are the same
if (e.size != written - locoff) {
throw new ZipException(
"invalid entry size (expected " + e.size +
" but got " + (written - locoff) + " bytes)");
}
if (e.crc != crc.getValue()) {
throw new ZipException(
"invalid entry crc-32 (expected 0x" +
Long.toHexString(e.crc) + " but got 0x" +
Long.toHexString(crc.getValue()) + ")");
break;
default:
throw new ZipException("invalid compression method");
}
break;
default:
throw new ZipException("invalid compression method");
crc.reset();
current = null;
} catch (IOException e) {
if (usesDefaultDeflater && !(e instanceof ZipException))
def.end();
throw e;
}
crc.reset();
current = null;
}
}

Expand Down
147 changes: 147 additions & 0 deletions jdk/test/java/util/zip/CloseDeflaterTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

/**
* @test
* @bug 8193682
* @summary Test Infinite loop while writing on closed GZipOutputStream , ZipOutputStream and JarOutputStream.
* @run testng CloseDeflaterTest
*/
import java.io.*;
import java.util.Random;
import java.util.jar.JarOutputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipOutputStream;
import java.util.zip.ZipEntry;

import org.testng.annotations.BeforeTest;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.fail;


public class CloseDeflaterTest {

//number of bytes to write
private static final int INPUT_LENGTH= 512;
//OutputStream that will throw an exception during a write operation
private static OutputStream outStream = new OutputStream() {
@Override
public void write(byte[] b, int off, int len) throws IOException {
//throw exception during write
throw new IOException();
}
@Override
public void write(byte b[]) throws IOException {}
@Override
public void write(int b) throws IOException {}
};
private static byte[] inputBytes = new byte[INPUT_LENGTH];
private static Random rand = new Random();

@DataProvider(name = "testgzipinput")
public Object[][] testGZipInput() {
//testGZip will close the GZipOutputStream using close() method when the boolean
//useCloseMethod is set to true and finish() method if the value is set to false
return new Object[][] {
{ GZIPOutputStream.class, true },
{ GZIPOutputStream.class, false },
};
}

@DataProvider(name = "testzipjarinput")
public Object[][] testZipAndJarInput() {
//testZipAndJarInput will perfrom write/closeEntry operations on JarOutputStream when the boolean
//useJar is set to true and on ZipOutputStream if the value is set to false
return new Object[][] {
{ JarOutputStream.class, true },
{ ZipOutputStream.class, false },
};
}

@BeforeTest
public void before_test()
{
//add inputBytes array with random bytes to write into Zip
rand.nextBytes(inputBytes);
}

//Test for infinite loop by writing bytes to closed GZIPOutputStream
@Test(dataProvider = "testgzipinput")
public void testGZip(Class<?> type, boolean useCloseMethod) throws IOException {
GZIPOutputStream zip = new GZIPOutputStream(outStream);
try {
zip.write(inputBytes, 0, INPUT_LENGTH);
//close zip
if(useCloseMethod) {
zip.close();
} else {
zip.finish();
}
} catch (IOException e) {
//expected
}
for (int i = 0; i < 3; i++) {
try {
//write on a closed GZIPOutputStream
zip.write(inputBytes, 0, INPUT_LENGTH);
fail("Deflater closed exception not thrown");
} catch (NullPointerException e) {
//expected , Deflater has been closed exception
}
}
}

//Test for infinite loop by writing bytes to closed ZipOutputStream/JarOutputStream
@Test(dataProvider = "testzipjarinput")
public void testZipCloseEntry(Class<?> type,boolean useJar) throws IOException {
ZipOutputStream zip = null;
if(useJar) {
zip = new JarOutputStream(outStream);
} else {
zip = new ZipOutputStream(outStream);
}
try {
zip.putNextEntry(new ZipEntry(""));
} catch (IOException e) {
//expected to throw IOException since putNextEntry calls write method
}
try {
zip.write(inputBytes, 0, INPUT_LENGTH);
//close zip entry
zip.closeEntry();
} catch (IOException e) {
//expected
}
for (int i = 0; i < 3; i++) {
try {
//write on a closed ZipOutputStream
zip.write(inputBytes, 0, INPUT_LENGTH);
fail("Deflater closed exception not thrown");
} catch (NullPointerException e) {
//expected , Deflater has been closed exception
}
}
}

}