#652: Avoid OOME for large values outside TIFF, even if length is unknown

(cherry picked from commit 33419ef291fcd6c3a2b706446bb41bdcd8da1d51)
This commit is contained in:
Harald Kuhr 2022-01-03 12:51:52 +01:00
parent 46db5a2fbf
commit 3c0b78549e

View File

@ -80,7 +80,7 @@ public final class TIFFReader extends MetadataReader {
private final Set<Long> parsedIFDs = new TreeSet<>(); private final Set<Long> parsedIFDs = new TreeSet<>();
private long length; private long inputLength;
private boolean longOffsets; private boolean longOffsets;
private int offsetSize; private int offsetSize;
@ -127,7 +127,7 @@ public final class TIFFReader extends MetadataReader {
throw new IIOException(String.format("Wrong TIFF magic in input data: %04x, expected: %04x", magic, TIFF.TIFF_MAGIC)); throw new IIOException(String.format("Wrong TIFF magic in input data: %04x, expected: %04x", magic, TIFF.TIFF_MAGIC));
} }
length = input.length(); inputLength = input.length();
return readLinkedIFDs(input); return readLinkedIFDs(input);
} }
@ -140,7 +140,7 @@ public final class TIFFReader extends MetadataReader {
// Read linked IFDs // Read linked IFDs
while (ifdOffset != 0) { while (ifdOffset != 0) {
try { try {
if ((length > 0 && ifdOffset >= length) || !parsedIFDs.add(ifdOffset)) { if ((inputLength > 0 && ifdOffset >= inputLength) || !isValidOffset(input, ifdOffset) || !parsedIFDs.add(ifdOffset)) {
// TODO: Issue warning // TODO: Issue warning
if (DEBUG) { if (DEBUG) {
System.err.println("Bad IFD offset: " + ifdOffset); System.err.println("Bad IFD offset: " + ifdOffset);
@ -218,7 +218,7 @@ public final class TIFFReader extends MetadataReader {
for (long ifdOffset : ifdOffsets) { for (long ifdOffset : ifdOffsets) {
try { try {
if ((length > 0 && ifdOffset >= length) || !parsedIFDs.add(ifdOffset)) { if ((inputLength > 0 && ifdOffset >= inputLength) || !isValidOffset(input, ifdOffset) || !parsedIFDs.add(ifdOffset)) {
// TODO: Issue warning // TODO: Issue warning
if (DEBUG) { if (DEBUG) {
System.err.println("Bad IFD offset: " + ifdOffset); System.err.println("Bad IFD offset: " + ifdOffset);
@ -330,20 +330,14 @@ public final class TIFFReader extends MetadataReader {
long valueLength = getValueLength(type, count); long valueLength = getValueLength(type, count);
Object value; Object value;
if (valueLength > 0 && valueLength <= offsetSize) { if (valueLength > 0 && valueLength <= offsetSize) {
value = readValueInLine(pInput, type, count); value = readValueInLine(pInput, type, count);
pInput.skipBytes(offsetSize - valueLength); pInput.skipBytes(offsetSize - valueLength);
} }
else { else {
long valueOffset = readOffset(pInput); // This is the *value* iff the value size is <= offsetSize long valueOffset = readOffset(pInput); // This is the *value* iff the value size is <= offsetSize
value = readValueAt(pInput, valueOffset, valueLength, type, count);
// Note: This a precaution
if (count >= Integer.MAX_VALUE || length > 0 && length < valueOffset + valueLength) {
value = new EOFException(String.format("TIFF value offset or size too large: %d/%d bytes (length: %d bytes)", valueOffset, valueLength, length));
}
else {
value = readValueAt(pInput, valueOffset, type, count);
}
} }
return new TIFFEntry(tagId, type, value); return new TIFFEntry(tagId, type, value);
@ -365,18 +359,52 @@ public final class TIFFReader extends MetadataReader {
return (int) count; return (int) count;
} }
private Object readValueAt(final ImageInputStream pInput, final long pOffset, final short pType, final int pCount) throws IOException { private boolean isValidOffset(final ImageInputStream input, final long pos) throws IOException {
long pos = pInput.getStreamPosition(); // TODO: If the position returns false, we could limit the length to pos for further reads...
try { try {
pInput.seek(pOffset); input.mark();
return readValue(pInput, pType, pCount, longOffsets); input.seek(pos);
return input.read() >= 0;
}
catch (IOException e) {
return false;
}
finally {
input.reset();
}
}
private boolean isValidLengthAtOffset(final ImageInputStream input, long offset, long valueLength) throws IOException {
// NOTE: For values smaller than Short.MAX_VALUE, we simply try, and handle the potential EOFException when reading
return (inputLength < 0 || inputLength >= offset + valueLength)
&& (valueLength < Short.MAX_VALUE || isValidOffset(input, offset + valueLength - 1));
}
private Object readValueAt(final ImageInputStream input, final long offset, final long length, final short type, final int count) throws IOException {
long pos = input.getStreamPosition();
try {
input.seek(offset);
// Avoid OOME due to corrupted/malicious data
if (count < Integer.MAX_VALUE && isValidLengthAtOffset(input, offset, length)) {
return readValue(input, type, count, longOffsets);
}
else {
throw new EOFException(String.format("TIFF value offset or size too large: @%08x/%d bytes (input length: %s)", offset, length, inputLength >= 0 ? inputLength + " bytes" : "unknown"));
}
} }
catch (EOFException e) { catch (EOFException e) {
// TODO: Add warning listener API and report problem to client code // TODO: Add warning listener API and report problem to client code
if (DEBUG) {
System.err.println(e);
}
return e; return e;
} }
finally { finally {
pInput.seek(pos); input.seek(pos);
} }
} }