diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Application.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Application.java index 80e07237..586c91d8 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Application.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Application.java @@ -90,7 +90,7 @@ class Application extends Segment { default: // Generic APPn segment - byte[] bytes = new byte[length - 2]; + byte[] bytes = new byte[Math.max(0, length - 2)]; data.readFully(bytes); return new Application(marker, identifier, bytes); } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java index 52e420f1..eadd04ca 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java @@ -233,7 +233,14 @@ public final class JPEGImageReader extends ImageReaderBase { @Override public Iterator getImageTypes(int imageIndex) throws IOException { - Iterator types = delegate.getImageTypes(imageIndex); + Iterator types; + try { + types = delegate.getImageTypes(imageIndex); + } + catch (IndexOutOfBoundsException | NegativeArraySizeException ignore) { + types = null; + } + JPEGColorSpace csType = getSourceCSType(getJFIF(), getAdobeDCT(), getSOF()); if (types == null || !types.hasNext() || csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK) { @@ -302,7 +309,7 @@ public final class JPEGImageReader extends ImageReaderBase { return rawType; } } - catch (IIOException | NullPointerException ignore) { + catch (IIOException | NullPointerException | ArrayIndexOutOfBoundsException | NegativeArraySizeException ignore) { // Fall through } @@ -933,7 +940,13 @@ public final class JPEGImageReader extends ImageReaderBase { return new JPEGLosslessDecoderWrapper(this).readRaster(segments, imageInput); } - return delegate.readRaster(imageIndex, param); + try { + return delegate.readRaster(imageIndex, param); + } + catch (IndexOutOfBoundsException knownIssue) { + // com.sun.imageio.plugins.jpeg.JPEGBuffer doesn't do proper sanity check of input data. + throw new IIOException("Corrupt JPEG data: Bad segment length", knownIssue); + } } @Override diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java index e8762f5a..533f308e 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java @@ -40,6 +40,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import static com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil.isKnownJPEGMarker; import static com.twelvemonkeys.lang.Validate.notNull; import static java.util.Arrays.copyOf; @@ -105,24 +106,25 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl { // Scan forward while (true) { - long realPosition = stream.getStreamPosition(); - int trash = 0; int marker = stream.readUnsignedByte(); - // Skip bad padding before the marker - while (marker != 0xff) { - marker = stream.readUnsignedByte(); - trash++; - realPosition++; - } + while (!isKnownJPEGMarker(marker)) { + marker &= 0xff; - marker = 0xff00 | stream.readUnsignedByte(); + // Skip bad padding before the marker + while (marker != 0xff) { + marker = stream.readUnsignedByte(); + trash++; + } - // Skip over 0xff padding between markers - while (marker == 0xffff) { - realPosition++; marker = 0xff00 | stream.readUnsignedByte(); + + // Skip over 0xff padding between markers + while (marker == 0xffff) { + marker = 0xff00 | stream.readUnsignedByte(); + trash++; + } } if (trash != 0) { @@ -131,6 +133,8 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl { processWarningOccured(String.format("Corrupt JPEG data: %d extraneous bytes before marker 0x%02x", trash, marker & 0xff)); } + long realPosition = stream.getStreamPosition() - 2; + // We are now handling all important segments ourselves, except APP1/Exif and APP14/Adobe, // as these segments affects image decoding. boolean appSegmentMarker = isAppSegmentMarker(marker); @@ -380,6 +384,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl { fetchSegment(); } catch (EOFException ignore) { + segments.add(new Segment(0, segment.realEnd(), segment.end(), Integer.MAX_VALUE * 2L - segment.realEnd())); // This might happen if the segment lengths in the stream are bad. // We MUST leave internal state untouched in this case. // We ignore this exception here, but client code will get @@ -415,7 +420,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl { repositionAsNecessary(); long bytesLeft = segment.end() - streamPos; // If no more bytes after reposition, we're at EOF - int count = bytesLeft == 0 ? -1 : segment.read(stream, b, off + total, (int) Math.min(len - total, bytesLeft)); + int count = bytesLeft <= 0 ? -1 : segment.read(stream, b, off + total, (int) Math.min(len - total, bytesLeft)); if (count == -1) { // EOF diff --git a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java index 350703ad..2f28168e 100644 --- a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java +++ b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java @@ -119,8 +119,15 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest> 8 & 0xff) != 0xff) { @@ -192,7 +194,7 @@ public final class JPEGSegmentUtil { byte[] data; if (segmentIdentifiers.containsKey(marker)) { - data = new byte[length - 2]; + data = new byte[Math.max(0, length - 2)]; stream.readFully(data); } else { @@ -218,6 +220,57 @@ public final class JPEGSegmentUtil { return new JPEGSegment(marker, data, length); } + public static boolean isKnownJPEGMarker(final int marker) { + switch (marker) { + case JPEG.SOI: + case JPEG.EOI: + case JPEG.DHT: + case JPEG.SOS: + case JPEG.DQT: + case JPEG.COM: + case JPEG.SOF0: + case JPEG.SOF1: + case JPEG.SOF2: + case JPEG.SOF3: + case JPEG.SOF5: + case JPEG.SOF6: + case JPEG.SOF7: + case JPEG.SOF9: + case JPEG.SOF10: + case JPEG.SOF11: + case JPEG.SOF13: + case JPEG.SOF14: + case JPEG.SOF15: + case JPEG.SOF55: + case JPEG.APP0: + case JPEG.APP1: + case JPEG.APP2: + case JPEG.APP3: + case JPEG.APP4: + case JPEG.APP5: + case JPEG.APP6: + case JPEG.APP7: + case JPEG.APP8: + case JPEG.APP9: + case JPEG.APP10: + case JPEG.APP11: + case JPEG.APP12: + case JPEG.APP13: + case JPEG.APP14: + case JPEG.APP15: + case JPEG.DRI: + case JPEG.TEM: + case JPEG.DAC: + case JPEG.DHP: + case JPEG.DNL: + case JPEG.EXP: + case JPEG.LSE: + return true; + default: + return false; + } + } + private static class AllIdsList extends ArrayList { @Override public String toString() {