JDK-8191073 : JpegImageReader throws IndexOutOfBoundsException when trying to read image data from tables-only image
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.imageio
  • Affected Version: 8,9,10
  • Priority: P2
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic
  • CPU: x86_64
  • Submitted: 2017-11-01
  • Updated: 2019-07-17
  • Resolved: 2018-01-10
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 11 Other
11 b01Fixed openjdk7uFixed
Related Reports
Duplicate :  
Duplicate :  
Description
FULL PRODUCT VERSION :
openjdk version "9.0.1"
OpenJDK Runtime Environment (build 9.0.1+11)
OpenJDK 64-Bit Server VM (build 9.0.1+11, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Ubuntu Linux 64-bit

A DESCRIPTION OF THE PROBLEM :
The ImageReader com.sun.imageio.plugins.jpeg.JPEGImageReader throws an IndexOutOfBoundsException in calls from method checkTablesOnly() when reading a malformed JPEG file whose header indicates that it is tables-only. This happens when attempting to flush the image input stream up to the last image, but when the length of the list `imagePositions` is zero. This exception is not documented in the API call to ImageIO.read() or ImageReader.read(); instead, the reader should throw an IOException indicating that the image input stream is malformed.


This bug was found using AFL.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile and run the test program attached below.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
An IOException should be thrown (specifically, a javax.imagio.IIOException).
ACTUAL -
A java.lang.IndexOutOfBoundsException is thrown (on JDK 8 and below, an ArrayIndexOutOfBoundsException is thrown).

ERROR MESSAGES/STACK TRACES THAT OCCUR :
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
	at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:248)
	at java.base/java.util.Objects.checkIndex(Objects.java:372)
	at java.base/java.util.ArrayList.get(ArrayList.java:440)
	at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.checkTablesOnly(JPEGImageReader.java:378)
	at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.gotoImage(JPEGImageReader.java:493)
	at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.readHeader(JPEGImageReader.java:716)
	at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.readInternal(JPEGImageReader.java:1173)
	at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.read(JPEGImageReader.java:1153)
	at java.desktop/javax.imageio.ImageIO.read(ImageIO.java:1468)
	at java.desktop/javax.imageio.ImageIO.read(ImageIO.java:1363)
	at JpegReaderOOBIssue.main(JpegReaderOOBIssue.java:25)

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Base64;
import javax.imageio.ImageIO;

public class JpegReaderOOBIssue {

	// JPEG image test case (encoded as base64)
	private static String inputImageBase64 = "/9j/4IAQSkZJRgABAQEASABIAAD" +
	"/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI///////////////////////////" +
	"/////////////////////////2wBDAVVaWnhpeOuCguv////////////////////////" +
	"/////////////////////////////////////////////////wAARCAAgACADASIAAhE" +
	"BAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAECA//EACUQAQACAAUDBAMAAAAAAAAAAAE" +
	"AAhESITGxQXKSA2Fi0SIyUf/EABYBAQEBAAAAAAAAAAAAAAAAAAABA//EABcRAQEBAQA" +
	"AAAAAAAAAAAAAAAEAESH/2gAMAwEAAhEDEQA/ANf2VW2OKaWTqnRhl97eb9wrs91uWPE" +
	"BUX+EtmrssvvbzfuJWjVG2tg1svLLtgJ0Uxwmd96d5zE7tVdnutyxm5JVoo0u6rpXHdW" +
	"LP8PU8WIjtRuvVZN96d5zDP8AD1PFhre1Apc/Ida4RAdv/9k=";

	public static void main(String[] args) throws java.io.IOException {
		// Convert test case into input stream
		byte[] inputBytes = Base64.getDecoder().decode(inputImageBase64);
		InputStream in = new ByteArrayInputStream(inputBytes);

		// Attempt to read JPEG
		ImageIO.read(in); // Throws IndexOutOfBoundsException!
	}
}
---------- END SOURCE ----------


Comments
As of now in JPEGImageReader we don't maintain state where we will know if the given input stream is just tables-only. Without this state maintenance if we try to read input stream which is tables-only and try to access image information it will lead to undefined behavior. We should make following changes to make tables-only image to work properly: 1) In checkTablesOnly() function if seekForward flag is enabled and the input stream is just tables-only we should not try to access imagePositions varaible as it will be empty. imagePosition list is in 1:1 relationship with number on image indices available in given input stream. 2) In checkTablesOnly() function when we get to know that the given input stream is just tables only image we should maintain that state in a boolean variable so that if we try to access image information in gotoImage() we should throw an IIOException mentioning that there is no image data available.
03-01-2018

Based on https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html We expect inputStream to JPEGImageReader in following ways: 1) tables-only image info -> image info -> image info ... 2) tables-only image info 3) image info -> image info -> image info ... 4) image info If there is tables-only image it should come at starting of given input stream and input stream can contain only a "tables-only image info" too without "image info". Because for a given JPEGImageReader tables-only image info(stream metadata) can be initialized once and can be used later to read many other individual image input streams.
03-01-2018

Root cause : When we try to read the submitter provided input stream in readNativeHeader() function returns the first header as tablesonly image in checkTablesOnly() function. Because of this we dont update the start of this stream position as one of the imagePositions. After that we check if there are any image data available using hasNextImage() and it also returns false. Overall we have a stream which contains only streamMetadata(tables-only image) In the same checkTablesOnly() we try to get initial imagePosition for this input stream. But since it is empty and we try "imagePositions.get(imagePositions.size()-1)" it throws IndexOutOfBoundsException.
03-01-2018

JDK 9.0.1 Ubuntu LInux (64-bit) Verified this with JDK 8u151, 9.0.1 and 10 and could confirm the results as reported. Results: 8: FAIL 8u151: FAIL 9: FAIL 9.0.1: FAIL 10: FAIL Output with JDK 9.0.1: ======================= >java JpegReaderOOBIssue Exception in thread "main" java.lang.IndexOutOfBoundsException: Index -1 out-of-bounds for length 0 at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64) at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70) at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:248) at java.base/java.util.Objects.checkIndex(Objects.java:372) at java.base/java.util.ArrayList.get(ArrayList.java:440) at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.checkTablesOnly(JPEGImageReader.java:378) at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.gotoImage(JPEGImageReader.java:493) at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.readHeader(JPEGImageReader.java:716) at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.readInternal(JPEGImageReader.java:1173) at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.read(JPEGImageReader.java:1153) at java.desktop/javax.imageio.ImageIO.read(ImageIO.java:1468) at java.desktop/javax.imageio.ImageIO.read(ImageIO.java:1363) at JpegReaderOOBIssue.main(JpegReaderOOBIssue.java:25) To verify run the attached test case with respective JDK versions.
10-11-2017