mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-06 04:55:30 -04:00
Fixes an issue where the CCITTFaxDecoderStream could cause endless reading (and potential OOME)
This commit is contained in:
parent
7fc47a338c
commit
f4a5f57d52
@ -50,6 +50,7 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
// See TIFF 6.0 Specification, Section 10: "Modified Huffman Compression", page 43.
|
// See TIFF 6.0 Specification, Section 10: "Modified Huffman Compression", page 43.
|
||||||
|
|
||||||
private final int columns;
|
private final int columns;
|
||||||
|
private int rowsLeft;
|
||||||
private final byte[] decodedRow;
|
private final byte[] decodedRow;
|
||||||
|
|
||||||
private final boolean optionG32D;
|
private final boolean optionG32D;
|
||||||
@ -70,6 +71,19 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
|
|
||||||
private int lastChangingElement = 0;
|
private int lastChangingElement = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a CCITTFaxDecoderStream.
|
||||||
|
*
|
||||||
|
* @param stream the compressed CCITT stream.
|
||||||
|
* @param columns the number of columns in the stream.
|
||||||
|
* @param type the type of stream, must be one of {@code COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE},
|
||||||
|
* {@code COMPRESSION_CCITT_T4} or {@code COMPRESSION_CCITT_T6}.
|
||||||
|
* @param options CCITT T.4 or T.6 options.
|
||||||
|
*/
|
||||||
|
public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final long options) {
|
||||||
|
this(stream, columns, -1, type, options, type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a CCITTFaxDecoderStream.
|
* Creates a CCITTFaxDecoderStream.
|
||||||
* This constructor may be used for CCITT streams embedded in PDF files,
|
* This constructor may be used for CCITT streams embedded in PDF files,
|
||||||
@ -82,15 +96,20 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
* @param options CCITT T.4 or T.6 options.
|
* @param options CCITT T.4 or T.6 options.
|
||||||
* @param byteAligned enable byte alignment used in PDF files (EncodedByteAlign).
|
* @param byteAligned enable byte alignment used in PDF files (EncodedByteAlign).
|
||||||
*/
|
*/
|
||||||
public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type,
|
public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final long options, final boolean byteAligned) {
|
||||||
final long options, final boolean byteAligned) {
|
this(stream, columns, -1, type, options, byteAligned);
|
||||||
|
}
|
||||||
|
|
||||||
|
CCITTFaxDecoderStream(final InputStream stream, final int columns, final int rows, final int type, final long options, final boolean byteAligned) {
|
||||||
super(Validate.notNull(stream, "stream"));
|
super(Validate.notNull(stream, "stream"));
|
||||||
|
|
||||||
this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0");
|
this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0");
|
||||||
|
// -1 means as many rows the stream contains, otherwise pad up to 'rows' before EOF for compatibility with legacy CCITT streams
|
||||||
|
this.rowsLeft = Validate.isTrue(rows == -1 || rows > 0, rows, "height must be greater than 0");
|
||||||
this.type = Validate.isTrue(type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE ||
|
this.type = Validate.isTrue(type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE ||
|
||||||
type == TIFFExtension.COMPRESSION_CCITT_T4 ||
|
type == TIFFExtension.COMPRESSION_CCITT_T4 ||
|
||||||
type == TIFFExtension.COMPRESSION_CCITT_T6,
|
type == TIFFExtension.COMPRESSION_CCITT_T6,
|
||||||
type, "Only CCITT Modified Huffman RLE compression (2), CCITT T4 (3) or CCITT T6 (4) supported: %s");
|
type, "Only CCITT Modified Huffman RLE compression (2), CCITT T4 (3) or CCITT T6 (4) supported: %s");
|
||||||
|
|
||||||
// We know this is only used for b/w (1 bit)
|
// We know this is only used for b/w (1 bit)
|
||||||
decodedRow = new byte[(columns + 7) / 8];
|
decodedRow = new byte[(columns + 7) / 8];
|
||||||
@ -122,21 +141,7 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Validate.isTrue(!optionUncompressed, optionUncompressed,
|
Validate.isTrue(!optionUncompressed, optionUncompressed,
|
||||||
"CCITT GROUP 3/4 OPTION UNCOMPRESSED is not supported");
|
"CCITT GROUP 3/4 OPTION UNCOMPRESSED is not supported");
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a CCITTFaxDecoderStream.
|
|
||||||
*
|
|
||||||
* @param stream the compressed CCITT stream.
|
|
||||||
* @param columns the number of columns in the stream.
|
|
||||||
* @param type the type of stream, must be one of {@code COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE},
|
|
||||||
* {@code COMPRESSION_CCITT_T4} or {@code COMPRESSION_CCITT_T6}.
|
|
||||||
* @param options CCITT T.4 or T.6 options.
|
|
||||||
*/
|
|
||||||
public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type,
|
|
||||||
final long options) {
|
|
||||||
this(stream, columns, type, options, type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int findCompressionType(final int encodedType, final InputStream stream) throws IOException {
|
static int findCompressionType(final int encodedType, final InputStream stream) throws IOException {
|
||||||
@ -209,9 +214,18 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ...otherwise, just let client code try to read past the
|
if (rowsLeft > 0) {
|
||||||
// end of stream
|
// For
|
||||||
decodedLength = -1;
|
rowsLeft--;
|
||||||
|
|
||||||
|
Arrays.fill(decodedRow, (byte) 0);
|
||||||
|
decodedLength = decodedRow.length;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// ...otherwise, just let client code try to read past the
|
||||||
|
// end of stream
|
||||||
|
decodedLength = -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
decodedPos = 0;
|
decodedPos = 0;
|
||||||
@ -404,7 +418,7 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
int byteIndex = index / 8;
|
int byteIndex = index / 8;
|
||||||
|
|
||||||
while (index % 8 != 0 && (nextChange - index) > 0) {
|
while (index % 8 != 0 && (nextChange - index) > 0) {
|
||||||
decodedRow[byteIndex] |= (white ? 0 : 1 << (7 - ((index) % 8)));
|
decodedRow[byteIndex] |= (byte) (white ? 0 : 1 << (7 - ((index) % 8)));
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -424,7 +438,7 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
decodedRow[byteIndex] = 0;
|
decodedRow[byteIndex] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
decodedRow[byteIndex] |= (white ? 0 : 1 << (7 - ((index) % 8)));
|
decodedRow[byteIndex] |= (byte) (white ? 0 : 1 << (7 - ((index) % 8)));
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -435,6 +449,7 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
throw new IOException("Sum of run-lengths does not equal scan line width: " + index + " > " + columns);
|
throw new IOException("Sum of run-lengths does not equal scan line width: " + index + " > " + columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rowsLeft--;
|
||||||
decodedLength = (index + 7) / 8;
|
decodedLength = (index + 7) / 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -494,14 +509,14 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
@Override
|
@Override
|
||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
if (decodedLength < 0) {
|
if (decodedLength < 0) {
|
||||||
return 0x0;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decodedPos >= decodedLength) {
|
if (decodedPos >= decodedLength) {
|
||||||
fetch();
|
fetch();
|
||||||
|
|
||||||
if (decodedLength < 0) {
|
if (decodedLength < 0) {
|
||||||
return 0x0;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -511,16 +526,14 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
@Override
|
@Override
|
||||||
public int read(byte[] b, int off, int len) throws IOException {
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
if (decodedLength < 0) {
|
if (decodedLength < 0) {
|
||||||
Arrays.fill(b, off, off + len, (byte) 0x0);
|
return -1;
|
||||||
return len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decodedPos >= decodedLength) {
|
if (decodedPos >= decodedLength) {
|
||||||
fetch();
|
fetch();
|
||||||
|
|
||||||
if (decodedLength < 0) {
|
if (decodedLength < 0) {
|
||||||
Arrays.fill(b, off, off + len, (byte) 0x0);
|
return -1;
|
||||||
return len;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1134,7 +1134,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
int compressedStripTileWidth = planarConfiguration == TIFFExtension.PLANARCONFIG_PLANAR && b > 0 && yCbCrSubsampling != null
|
int compressedStripTileWidth = planarConfiguration == TIFFExtension.PLANARCONFIG_PLANAR && b > 0 && yCbCrSubsampling != null
|
||||||
? ((stripTileWidth + yCbCrSubsampling[0] - 1) / yCbCrSubsampling[0])
|
? ((stripTileWidth + yCbCrSubsampling[0] - 1) / yCbCrSubsampling[0])
|
||||||
: stripTileWidth;
|
: stripTileWidth;
|
||||||
adapter = createDecompressorStream(compression, compressedStripTileWidth, samplesInTile, adapter);
|
adapter = createDecompressorStream(compression, compressedStripTileWidth, stripTileHeight, samplesInTile, adapter);
|
||||||
adapter = createUnpredictorStream(predictor, compressedStripTileWidth, samplesInTile, bitsPerSample, adapter, imageInput.getByteOrder());
|
adapter = createUnpredictorStream(predictor, compressedStripTileWidth, samplesInTile, bitsPerSample, adapter, imageInput.getByteOrder());
|
||||||
adapter = createYCbCrUpsamplerStream(interpretation, planarConfiguration, b, rowRaster.getTransferType(), yCbCrSubsampling, yCbCrPos, colsInTile, adapter, imageInput.getByteOrder());
|
adapter = createYCbCrUpsamplerStream(interpretation, planarConfiguration, b, rowRaster.getTransferType(), yCbCrSubsampling, yCbCrPos, colsInTile, adapter, imageInput.getByteOrder());
|
||||||
|
|
||||||
@ -2506,7 +2506,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
return (short) Math.max(0, Math.min(0xffff, val));
|
return (short) Math.max(0, Math.min(0xffff, val));
|
||||||
}
|
}
|
||||||
|
|
||||||
private InputStream createDecompressorStream(final int compression, final int width, final int bands, InputStream stream) throws IOException {
|
private InputStream createDecompressorStream(final int compression, final int width, final int height, final int bands, InputStream stream) throws IOException {
|
||||||
switch (compression) {
|
switch (compression) {
|
||||||
case TIFFBaseline.COMPRESSION_NONE:
|
case TIFFBaseline.COMPRESSION_NONE:
|
||||||
return stream;
|
return stream;
|
||||||
@ -2527,7 +2527,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
if (overrideCCITTCompression == -1) {
|
if (overrideCCITTCompression == -1) {
|
||||||
overrideCCITTCompression = findCCITTType(compression, stream);
|
overrideCCITTCompression = findCCITTType(compression, stream);
|
||||||
}
|
}
|
||||||
return new CCITTFaxDecoderStream(stream, width, overrideCCITTCompression, getCCITTOptions(compression), compression == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE);
|
return new CCITTFaxDecoderStream(stream, width, height, overrideCCITTCompression, getCCITTOptions(compression), compression == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE);
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unsupported TIFF compression: " + compression);
|
throw new IllegalArgumentException("Unsupported TIFF compression: " + compression);
|
||||||
}
|
}
|
||||||
|
@ -157,8 +157,13 @@ public class CCITTFaxDecoderStreamTest {
|
|||||||
|
|
||||||
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||||
byte[] bytes = new byte[imageData.length];
|
byte[] bytes = new byte[imageData.length];
|
||||||
new DataInputStream(stream).readFully(bytes);
|
|
||||||
|
DataInputStream dataInputStream = new DataInputStream(stream);
|
||||||
|
dataInputStream.readFully(bytes);
|
||||||
assertArrayEquals(imageData, bytes);
|
assertArrayEquals(imageData, bytes);
|
||||||
|
|
||||||
|
assertEquals(-1, dataInputStream.read());
|
||||||
|
assertEquals(-1, dataInputStream.read(new byte[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -168,8 +173,13 @@ public class CCITTFaxDecoderStreamTest {
|
|||||||
|
|
||||||
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||||
byte[] bytes = new byte[imageData.length];
|
byte[] bytes = new byte[imageData.length];
|
||||||
new DataInputStream(stream).readFully(bytes);
|
|
||||||
|
DataInputStream dataInputStream = new DataInputStream(stream);
|
||||||
|
dataInputStream.readFully(bytes);
|
||||||
assertArrayEquals(imageData, bytes);
|
assertArrayEquals(imageData, bytes);
|
||||||
|
|
||||||
|
assertEquals(-1, dataInputStream.read());
|
||||||
|
assertEquals(-1, dataInputStream.read(new byte[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -179,8 +189,13 @@ public class CCITTFaxDecoderStreamTest {
|
|||||||
|
|
||||||
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||||
byte[] bytes = new byte[imageData.length];
|
byte[] bytes = new byte[imageData.length];
|
||||||
new DataInputStream(stream).readFully(bytes);
|
|
||||||
|
DataInputStream dataInputStream = new DataInputStream(stream);
|
||||||
|
dataInputStream.readFully(bytes);
|
||||||
assertArrayEquals(imageData, bytes);
|
assertArrayEquals(imageData, bytes);
|
||||||
|
|
||||||
|
assertEquals(-1, dataInputStream.read());
|
||||||
|
assertEquals(-1, dataInputStream.read(new byte[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -265,7 +280,7 @@ public class CCITTFaxDecoderStreamTest {
|
|||||||
new DataInputStream(inputStream).readFully(data);
|
new DataInputStream(inputStream).readFully(data);
|
||||||
|
|
||||||
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(data),
|
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(data),
|
||||||
6, TIFFExtension.COMPRESSION_CCITT_T6, 0L);
|
6, 6, TIFFExtension.COMPRESSION_CCITT_T6, 0L, false);
|
||||||
|
|
||||||
byte[] bytes = new byte[6]; // 6 x 6 pixel, 1 bpp => 6 bytes
|
byte[] bytes = new byte[6]; // 6 x 6 pixel, 1 bpp => 6 bytes
|
||||||
new DataInputStream(stream).readFully(bytes);
|
new DataInputStream(stream).readFully(bytes);
|
||||||
@ -274,8 +289,8 @@ public class CCITTFaxDecoderStreamTest {
|
|||||||
byte[] imageData = Arrays.copyOf(((DataBufferByte) image.getData().getDataBuffer()).getData(), 6);
|
byte[] imageData = Arrays.copyOf(((DataBufferByte) image.getData().getDataBuffer()).getData(), 6);
|
||||||
assertArrayEquals(imageData, bytes);
|
assertArrayEquals(imageData, bytes);
|
||||||
|
|
||||||
// Ideally, we should have no more data now, but the stream don't know that...
|
assertEquals("Should contain no more data", -1, stream.read());
|
||||||
// assertEquals("Should contain no more data", -1, stream.read());
|
assertEquals("Should contain no more data", -1, stream.read(new byte[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
Loading…
x
Reference in New Issue
Block a user