8287102: ImageReaderSpi.canDecodeInput() for standard plugins should return false if a stream is too short

Reviewed-by: prr
This commit is contained in:
Martin Desruisseaux 2022-06-03 21:16:21 +00:00 committed by Phil Race
parent 7a0c8b14aa
commit a7e07fdbc1
8 changed files with 141 additions and 21 deletions
src/java.desktop/share/classes/com/sun/imageio/plugins
test/jdk/javax/imageio/plugins

@ -28,11 +28,11 @@ package com.sun.imageio.plugins.bmp;
import java.util.Locale;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ServiceRegistry;
import java.io.IOException;
import javax.imageio.ImageReader;
import javax.imageio.IIOException;
import com.sun.imageio.plugins.common.ReaderUtil;
public class BMPImageReaderSpi extends ImageReaderSpi {
@ -81,10 +81,10 @@ public class BMPImageReaderSpi extends ImageReaderSpi {
ImageInputStream stream = (ImageInputStream)source;
byte[] b = new byte[2];
stream.mark();
stream.readFully(b);
boolean full = ReaderUtil.tryReadFully(stream, b);
stream.reset();
return (b[0] == 0x42) && (b[1] == 0x4d);
return full && (b[0] == 0x42) && (b[1] == 0x4d);
}
public ImageReader createReaderInstance(Object extension)

@ -258,4 +258,28 @@ public class ReaderUtil {
}
return decodedData;
}
/**
* Tries to read {@code b.length} bytes from the stream,
* and stores them into {@code b} starting at index 0.
* If the end of the stream is reached, a {@code false}
* will be returned.
*
* @param iis the stream to read.
* @param b an array where to store the {@code byte}s.
* @return {@code true} on success, or {@code false} on EOF.
*/
public static boolean tryReadFully(ImageInputStream iis, byte[] b)
throws IOException
{
int offset = 0;
do {
int n = iis.read(b, offset, b.length - offset);
if (n < 0) {
return false; // EOF
}
offset += n;
} while (offset < b.length);
return true;
}
}

@ -27,12 +27,10 @@ package com.sun.imageio.plugins.gif;
import java.io.IOException;
import java.util.Locale;
import java.util.Iterator;
import javax.imageio.ImageReader;
import javax.imageio.metadata.IIOMetadataFormat;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import com.sun.imageio.plugins.common.ReaderUtil;
public class GIFImageReaderSpi extends ImageReaderSpi {
@ -85,10 +83,11 @@ public class GIFImageReaderSpi extends ImageReaderSpi {
ImageInputStream stream = (ImageInputStream)input;
byte[] b = new byte[6];
stream.mark();
stream.readFully(b);
boolean full = ReaderUtil.tryReadFully(stream, b);
stream.reset();
return b[0] == 'G' && b[1] == 'I' && b[2] == 'F' && b[3] == '8' &&
return full &&
b[0] == 'G' && b[1] == 'I' && b[2] == 'F' && b[3] == '8' &&
(b[4] == '7' || b[4] == '9') && b[5] == 'a';
}

@ -27,12 +27,10 @@ package com.sun.imageio.plugins.png;
import java.io.IOException;
import java.util.Locale;
import java.util.Iterator;
import javax.imageio.ImageReader;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.metadata.IIOMetadataFormat;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.stream.ImageInputStream;
import com.sun.imageio.plugins.common.ReaderUtil;
public class PNGImageReaderSpi extends ImageReaderSpi {
@ -84,10 +82,11 @@ public class PNGImageReaderSpi extends ImageReaderSpi {
ImageInputStream stream = (ImageInputStream)input;
byte[] b = new byte[8];
stream.mark();
stream.readFully(b);
boolean full = ReaderUtil.tryReadFully(stream, b);
stream.reset();
return (b[0] == (byte)137 &&
return full &&
(b[0] == (byte)137 &&
b[1] == (byte)80 &&
b[2] == (byte)78 &&
b[3] == (byte)71 &&

@ -30,6 +30,7 @@ import javax.imageio.ImageReader;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ServiceRegistry;
import javax.imageio.stream.ImageInputStream;
import com.sun.imageio.plugins.common.ReaderUtil;
public class TIFFImageReaderSpi extends ImageReaderSpi {
@ -67,10 +68,11 @@ public class TIFFImageReaderSpi extends ImageReaderSpi {
ImageInputStream stream = (ImageInputStream)input;
byte[] b = new byte[4];
stream.mark();
stream.readFully(b);
boolean full = ReaderUtil.tryReadFully(stream, b);
stream.reset();
return ((b[0] == (byte)0x49 && b[1] == (byte)0x49 &&
return full &&
((b[0] == (byte)0x49 && b[1] == (byte)0x49 &&
b[2] == (byte)0x2a && b[3] == (byte)0x00) ||
(b[0] == (byte)0x4d && b[1] == (byte)0x4d &&
b[2] == (byte)0x00 && b[3] == (byte)0x2a));

@ -28,12 +28,10 @@ package com.sun.imageio.plugins.wbmp;
import java.util.Locale;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ServiceRegistry;
import java.io.IOException;
import javax.imageio.ImageReader;
import javax.imageio.IIOException;
import com.sun.imageio.plugins.common.ReaderUtil;
public class WBMPImageReaderSpi extends ImageReaderSpi {
@ -86,16 +84,16 @@ public class WBMPImageReaderSpi extends ImageReaderSpi {
stream.mark();
try {
int type = stream.readByte(); // TypeField
int fixHeaderField = stream.readByte();
int type = stream.read(); // TypeField, or -1 if EOF
int fixHeaderField = stream.read();
// check WBMP "header"
if (type != 0 || fixHeaderField != 0) {
// while WBMP reader does not support ext WBMP headers
return false;
}
int width = ReaderUtil.readMultiByteInteger(stream);
int height = ReaderUtil.readMultiByteInteger(stream);
int width = tryReadMultiByteInteger(stream);
int height = tryReadMultiByteInteger(stream);
// check image dimension
if (width <= 0 || height <= 0) {
return false;
@ -123,6 +121,34 @@ public class WBMPImageReaderSpi extends ImageReaderSpi {
}
}
/**
* Reads a positive integer value encoded on a variable number of bytes,
* but stops the reading on end-of-file (EOF) or on integer overflow.
*
* @param stream the image input stream to read.
* @return the integer value, or -1 if EOF or integer overflow.
*/
private static int tryReadMultiByteInteger(ImageInputStream stream)
throws IOException {
int value = stream.read();
if (value < 0) {
return -1; // EOF
}
int result = value & 0x7f;
while ((value & 0x80) == 0x80) {
if ((result & 0xfe000000) != 0) {
return -1; // 7 highest bits already used
}
result <<= 7;
value = stream.read();
if (value < 0) {
return -1; // EOF
}
result |= (value & 0x7f);
}
return result;
}
public ImageReader createReaderInstance(Object extension)
throws IIOException {
return new WBMPImageReader(this);

@ -0,0 +1,69 @@
/*
* Copyright (c) 2022, 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
* @summary Verifies that canDecode does not throw EOFException
* if the file has too few bytes.
* @run main CanDecodeTest
*/
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
public class CanDecodeTest {
private static final String[] FORMATS = {
"WBMP", "BMP", "GIF", "PNG", "TIFF", "JPEG"
};
public static void main(String[] args) {
for (String format : FORMATS) {
ImageReader reader =
ImageIO.getImageReadersByFormatName(format).next();
ImageReaderSpi spi = reader.getOriginatingProvider();
for (int n=0; n<8; n++) {
InputStream dataStream =
new ByteArrayInputStream(new byte[n]);
try {
ImageInputStream iis =
ImageIO.createImageInputStream(dataStream);
if (spi.canDecodeInput(iis)) {
throw new RuntimeException("Test failed for " +
format + " format: shall not decode.");
}
} catch (IOException e) {
throw new RuntimeException("Test failed for " +
format + " format: " + e, e);
}
}
}
}
}

@ -66,6 +66,7 @@ public class CanDecodeTest {
(byte) 0x0a, (byte) 0x00}, 39693, false));
v.add(new TestCase("ico", new byte[]{(byte) 0x00, (byte) 0x00,
(byte) 0x01, (byte) 0x00}, 1078, false));
v.add(new TestCase("empty", new byte[0], 0, false));
return v;
}