From 49371caff0294b474d97ba11fb9c2bb166052d66 Mon Sep 17 00:00:00 2001 From: Jaesung Chung Date: Mon, 14 Mar 2016 14:57:54 +0900 Subject: [PATCH] ExifInterface: handle the invalid offsets and count numbers Some JPEG images generated by various camera vendors have wrong offsets and invalid numbers for indicating the size of components. Instead of throwing exceptions, this CL makes Exifinterface ignore these cases to read the information without losing contents already parsed. Bug: 27583378 Change-Id: Ie8ee0bf49283ef519f4f31c5b8ba78ce3f82fe92 --- media/java/android/media/ExifInterface.java | 137 ++++++++++++++------ 1 file changed, 94 insertions(+), 43 deletions(-) diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index ce12e76b65880..e35fcf62f6af3 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -1197,8 +1197,9 @@ public class ExifInterface { } bytesRead += 2; int length = dataInputStream.readUnsignedShort() - 2; - if (length < 0) + if (length < 0) { throw new IOException("Invalid length"); + } bytesRead += length; switch (marker) { case MARKER_APP1: { @@ -1221,6 +1222,9 @@ public class ExifInterface { if (length <= 0) { throw new IOException("Invalid exif"); } + if (DEBUG) { + Log.d(TAG, "readExifSegment with a byte array (length: " + length + ")"); + } byte[] bytes = new byte[length]; if (dataInputStream.read(bytes) != length) { throw new IOException("Invalid exif"); @@ -1309,8 +1313,9 @@ public class ExifInterface { case MARKER_APP1: { // Rewrite EXIF segment int length = dataInputStream.readUnsignedShort() - 2; - if (length < 0) + if (length < 0) { throw new IOException("Invalid length"); + } bytesRead += 2; int read; while ((read = dataInputStream.read( @@ -1331,8 +1336,9 @@ public class ExifInterface { // Copy JPEG segment int length = dataInputStream.readUnsignedShort(); dataOutputStream.writeUnsignedShort(length); - if (length < 0) + if (length < 0) { throw new IOException("Invalid length"); + } length -= 2; bytesRead += 2; int read; @@ -1385,8 +1391,9 @@ public class ExifInterface { } firstIfdOffset -= 8; if (firstIfdOffset > 0) { - if (dataInputStream.skip(firstIfdOffset) != firstIfdOffset) + if (dataInputStream.skip(firstIfdOffset) != firstIfdOffset) { throw new IOException("Couldn't jump to first Ifd: " + firstIfdOffset); + } } // Read primary image TIFF image file directory. @@ -1582,8 +1589,16 @@ public class ExifInterface { // Reads image file directory, which is a tag group in EXIF. private void readImageFileDirectory(ByteOrderAwarenessDataInputStream dataInputStream, int hint) throws IOException { + if (dataInputStream.peek() + 2 > dataInputStream.mLength) { + // Return if there is no data from the offset. + return; + } // See JEITA CP-3451 Figure 5. page 9. short numberOfDirectoryEntry = dataInputStream.readShort(); + if (dataInputStream.peek() + 12 * numberOfDirectoryEntry > dataInputStream.mLength) { + // Return if the size of entries is too big. + return; + } if (DEBUG) { Log.d(TAG, "numberOfDirectoryEntry: " + numberOfDirectoryEntry); @@ -1595,10 +1610,25 @@ public class ExifInterface { int numberOfComponents = dataInputStream.readInt(); long nextEntryOffset = dataInputStream.peek() + 4; // next four bytes is for data // offset or value. + // Look up a corresponding tag from tag number + String tagName = (String) sExifTagMapsForReading[hint].get(tagNumber); if (DEBUG) { - Log.d(TAG, String.format("tagNumber: %d, dataFormat: %d, numberOfComponents: %d", - tagNumber, dataFormat, numberOfComponents)); + Log.d(TAG, String.format("hint: %d, tagNumber: %d, tagName: %s, dataFormat: %d," + + "numberOfComponents: %d", hint, tagNumber, tagName, dataFormat, + numberOfComponents)); + } + + if (tagName == null || dataFormat <= 0 || + dataFormat >= IFD_FORMAT_BYTES_PER_FORMAT.length) { + // Skip if the parsed tag number is not defined or invalid data format. + if (tagName == null) { + Log.w(TAG, "Skip the tag entry since tag number is not defined: " + tagNumber); + } else { + Log.w(TAG, "Skip the tag entry since data format is invalid: " + dataFormat); + } + dataInputStream.seek(nextEntryOffset); + continue; } // Read a value from data field or seek to the value offset which is stored in data @@ -1609,19 +1639,21 @@ public class ExifInterface { if (DEBUG) { Log.d(TAG, "seek to data offset: " + offset); } - dataInputStream.seek(offset); - } - - // Look up a corresponding tag from tag number - String tagName = (String) sExifTagMapsForReading[hint].get(tagNumber); - // Skip if the parsed tag number is not defined. - if (tagName == null) { - dataInputStream.seek(nextEntryOffset); - continue; + if (offset + byteCount <= dataInputStream.mLength) { + dataInputStream.seek(offset); + } else { + // Skip if invalid data offset. + Log.w(TAG, "Skip the tag entry since data offset is invalid: " + offset); + dataInputStream.seek(nextEntryOffset); + continue; + } } // Recursively parse IFD when a IFD pointer tag appears. int innerIfdHint = getIfdHintFromTagNumber(tagNumber); + if (DEBUG) { + Log.d(TAG, "innerIfdHint: " + innerIfdHint + " byteCount: " + byteCount); + } if (innerIfdHint >= 0) { long offset = -1L; // Get offset from data field @@ -1650,9 +1682,11 @@ public class ExifInterface { if (DEBUG) { Log.d(TAG, String.format("Offset: %d, tagName: %s", offset, tagName)); } - if (offset > 0L) { + if (offset > 0L && offset < dataInputStream.mLength) { dataInputStream.seek(offset); readImageFileDirectory(dataInputStream, innerIfdHint); + } else { + Log.w(TAG, "Skip jump into the IFD since its offset is invalid: " + offset); } dataInputStream.seek(nextEntryOffset); @@ -1683,14 +1717,17 @@ public class ExifInterface { } } - long nextIfdOffset = dataInputStream.readUnsignedInt(); - if (DEBUG) { - Log.d(TAG, String.format("nextIfdOffset: %d", nextIfdOffset)); - } - // The next IFD offset needs to be bigger than 8 since the first IFD offset is at least 8. - if (nextIfdOffset > 8) { - dataInputStream.seek(nextIfdOffset); - readImageFileDirectory(dataInputStream, IFD_THUMBNAIL_HINT); + if (dataInputStream.peek() + 4 <= dataInputStream.mLength) { + long nextIfdOffset = dataInputStream.readUnsignedInt(); + if (DEBUG) { + Log.d(TAG, String.format("nextIfdOffset: %d", nextIfdOffset)); + } + // The next IFD offset needs to be bigger than 8 + // since the first IFD offset is at least 8. + if (nextIfdOffset > 8 && nextIfdOffset < dataInputStream.mLength) { + dataInputStream.seek(nextIfdOffset); + readImageFileDirectory(dataInputStream, IFD_THUMBNAIL_HINT); + } } } @@ -1748,17 +1785,18 @@ public class ExifInterface { } StringBuilder stringBuilder = new StringBuilder(); - while (true) { + while (index < numberOfComponents) { int ch = bytes[index]; - if (ch == 0) + if (ch == 0) { break; - if (ch >= 32) + } + if (ch >= 32) { stringBuilder.append((char) ch); - else + } + else { stringBuilder.append('?'); + } ++index; - if (index == numberOfComponents) - break; } return stringBuilder.toString(); } @@ -1772,8 +1810,9 @@ public class ExifInterface { // Gets the corresponding IFD group index of the given tag number for writing Exif Tags. private static int getIfdHintFromTagNumber(int tagNumber) { for (int i = 0; i < IFD_POINTER_TAG_HINTS.length; ++i) { - if (IFD_POINTER_TAGS[i].number == tagNumber) + if (IFD_POINTER_TAGS[i].number == tagNumber) { return IFD_POINTER_TAG_HINTS[i]; + } } return -1; } @@ -2076,8 +2115,9 @@ public class ExifInterface { public void seek(long byteCount) throws IOException { mPosition = 0L; reset(); - if (skip(byteCount) != byteCount) + if (skip(byteCount) != byteCount) { throw new IOException("Couldn't seek up to the byteCount"); + } } public long peek() { @@ -2086,8 +2126,9 @@ public class ExifInterface { public void readFully(byte[] buffer) throws IOException { mPosition += buffer.length; - if (mPosition > mLength) + if (mPosition > mLength) { throw new EOFException(); + } if (super.read(buffer, 0, buffer.length) != buffer.length) { throw new IOException("Couldn't read up to the length of buffer"); } @@ -2095,22 +2136,26 @@ public class ExifInterface { public byte readByte() throws IOException { ++mPosition; - if (mPosition > mLength) + if (mPosition > mLength) { throw new EOFException(); + } int ch = super.read(); - if (ch < 0) + if (ch < 0) { throw new EOFException(); + } return (byte) ch; } public short readShort() throws IOException { mPosition += 2; - if (mPosition > mLength) + if (mPosition > mLength) { throw new EOFException(); + } int ch1 = super.read(); int ch2 = super.read(); - if ((ch1 | ch2) < 0) + if ((ch1 | ch2) < 0) { throw new EOFException(); + } if (mByteOrder == LITTLE_ENDIAN) { return (short) ((ch2 << 8) + (ch1)); } else if (mByteOrder == BIG_ENDIAN) { @@ -2121,14 +2166,16 @@ public class ExifInterface { public int readInt() throws IOException { mPosition += 4; - if (mPosition > mLength) + if (mPosition > mLength) { throw new EOFException(); + } int ch1 = super.read(); int ch2 = super.read(); int ch3 = super.read(); int ch4 = super.read(); - if ((ch1 | ch2 | ch3 | ch4) < 0) + if ((ch1 | ch2 | ch3 | ch4) < 0) { throw new EOFException(); + } if (mByteOrder == LITTLE_ENDIAN) { return ((ch4 << 24) + (ch3 << 16) + (ch2 << 8) + ch1); } else if (mByteOrder == BIG_ENDIAN) { @@ -2146,12 +2193,14 @@ public class ExifInterface { public int readUnsignedShort() throws IOException { mPosition += 2; - if (mPosition > mLength) + if (mPosition > mLength) { throw new EOFException(); + } int ch1 = super.read(); int ch2 = super.read(); - if ((ch1 | ch2) < 0) + if ((ch1 | ch2) < 0) { throw new EOFException(); + } if (mByteOrder == LITTLE_ENDIAN) { return ((ch2 << 8) + (ch1)); } else if (mByteOrder == BIG_ENDIAN) { @@ -2166,8 +2215,9 @@ public class ExifInterface { public long readLong() throws IOException { mPosition += 8; - if (mPosition > mLength) + if (mPosition > mLength) { throw new EOFException(); + } int ch1 = super.read(); int ch2 = super.read(); int ch3 = super.read(); @@ -2176,8 +2226,9 @@ public class ExifInterface { int ch6 = super.read(); int ch7 = super.read(); int ch8 = super.read(); - if ((ch1 | ch2 | ch3 | ch4 | ch5 | ch6 | ch7 | ch8) < 0) + if ((ch1 | ch2 | ch3 | ch4 | ch5 | ch6 | ch7 | ch8) < 0) { throw new EOFException(); + } if (mByteOrder == LITTLE_ENDIAN) { return (((long) ch8 << 56) + ((long) ch7 << 48) + ((long) ch6 << 40) + ((long) ch5 << 32) + ((long) ch4 << 24) + ((long) ch3 << 16)