Skip to content

Commit

Permalink
Handle faulty next IFD pointers
Browse files Browse the repository at this point in the history
In some files in the wild, non-0th IFDs have a pointer to a next IFD
even though that should not be possible. Only the 0th IFD should have 
this pointer and it is used for 1st IFD which is a thumbnail.

In the specific case of #248 those invalid pointers also pointed to the
same IFDs that contained them which caused an infinite loop.

#248
  • Loading branch information
mattiasw committed Oct 7, 2023
1 parent b2e7cdd commit 6039ea5
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 21 deletions.
2 changes: 1 addition & 1 deletion dist/exif-reader.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/exif-reader.js.map

Large diffs are not rendered by default.

18 changes: 13 additions & 5 deletions src/tag-names.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,18 @@ import TagNamesMpfIfd from './tag-names-mpf-ifd.js';

const tagNames0thExifIfds = objectAssign({}, TagNames0thIfd, TagNamesExifIfd);

export const IFD_TYPE_0TH = '0th';
export const IFD_TYPE_1ST = '1st';
export const IFD_TYPE_EXIF = 'exif';
export const IFD_TYPE_GPS = 'gps';
export const IFD_TYPE_INTEROPERABILITY = 'interoperability';
export const IFD_TYPE_MPF = 'mpf';

export default {
'0th': tagNames0thExifIfds,
'exif': tagNames0thExifIfds,
'gps': TagNamesGpsIfd,
'interoperability': TagNamesInteroperabilityIfd,
'mpf': Constants.USE_MPF ? TagNamesMpfIfd : {},
[IFD_TYPE_0TH]: tagNames0thExifIfds,
[IFD_TYPE_1ST]: TagNames0thIfd,
[IFD_TYPE_EXIF]: tagNames0thExifIfds,
[IFD_TYPE_GPS]: TagNamesGpsIfd,
[IFD_TYPE_INTEROPERABILITY]: TagNamesInteroperabilityIfd,
[IFD_TYPE_MPF]: Constants.USE_MPF ? TagNamesMpfIfd : {},
};
16 changes: 8 additions & 8 deletions src/tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Constants from './constants.js';
import {objectAssign} from './utils.js';
import ByteOrder from './byte-order.js';
import Types from './types.js';
import TagNames from './tag-names.js';
import TagNames, {IFD_TYPE_0TH, IFD_TYPE_1ST, IFD_TYPE_EXIF, IFD_TYPE_GPS, IFD_TYPE_INTEROPERABILITY, IFD_TYPE_MPF} from './tag-names.js';
import {deferInit, getBase64Image} from './utils.js';

const EXIF_IFD_POINTER_KEY = 'Exif IFD Pointer';
Expand Down Expand Up @@ -41,7 +41,7 @@ function read(dataView, tiffHeaderOffset, includeUnknown) {
}

function read0thIfd(dataView, tiffHeaderOffset, byteOrder, includeUnknown) {
return readIfd(dataView, '0th', tiffHeaderOffset, get0thIfdOffset(dataView, tiffHeaderOffset, byteOrder), byteOrder, includeUnknown);
return readIfd(dataView, IFD_TYPE_0TH, tiffHeaderOffset, get0thIfdOffset(dataView, tiffHeaderOffset, byteOrder), byteOrder, includeUnknown);
}

function get0thIfdOffset(dataView, tiffHeaderOffset, byteOrder) {
Expand All @@ -50,31 +50,31 @@ function get0thIfdOffset(dataView, tiffHeaderOffset, byteOrder) {

function readExifIfd(tags, dataView, tiffHeaderOffset, byteOrder, includeUnknown) {
if (tags[EXIF_IFD_POINTER_KEY] !== undefined) {
return objectAssign(tags, readIfd(dataView, 'exif', tiffHeaderOffset, tiffHeaderOffset + tags[EXIF_IFD_POINTER_KEY].value, byteOrder, includeUnknown));
return objectAssign(tags, readIfd(dataView, IFD_TYPE_EXIF, tiffHeaderOffset, tiffHeaderOffset + tags[EXIF_IFD_POINTER_KEY].value, byteOrder, includeUnknown));
}

return tags;
}

function readGpsIfd(tags, dataView, tiffHeaderOffset, byteOrder, includeUnknown) {
if (tags[GPS_INFO_IFD_POINTER_KEY] !== undefined) {
return objectAssign(tags, readIfd(dataView, 'gps', tiffHeaderOffset, tiffHeaderOffset + tags[GPS_INFO_IFD_POINTER_KEY].value, byteOrder, includeUnknown));
return objectAssign(tags, readIfd(dataView, IFD_TYPE_GPS, tiffHeaderOffset, tiffHeaderOffset + tags[GPS_INFO_IFD_POINTER_KEY].value, byteOrder, includeUnknown));
}

return tags;
}

function readInteroperabilityIfd(tags, dataView, tiffHeaderOffset, byteOrder, includeUnknown) {
if (tags[INTEROPERABILITY_IFD_POINTER_KEY] !== undefined) {
return objectAssign(tags, readIfd(dataView, 'interoperability', tiffHeaderOffset, tiffHeaderOffset + tags[INTEROPERABILITY_IFD_POINTER_KEY].value, byteOrder, includeUnknown));
return objectAssign(tags, readIfd(dataView, IFD_TYPE_INTEROPERABILITY, tiffHeaderOffset, tiffHeaderOffset + tags[INTEROPERABILITY_IFD_POINTER_KEY].value, byteOrder, includeUnknown));
}

return tags;
}

function readMpf(dataView, dataOffset, includeUnknown) {
const byteOrder = ByteOrder.getByteOrder(dataView, dataOffset);
const tags = readIfd(dataView, 'mpf', dataOffset, get0thIfdOffset(dataView, dataOffset, byteOrder), byteOrder, includeUnknown);
const tags = readIfd(dataView, IFD_TYPE_MPF, dataOffset, get0thIfdOffset(dataView, dataOffset, byteOrder), byteOrder, includeUnknown);
return addMpfImages(dataView, dataOffset, tags, byteOrder);
}

Expand Down Expand Up @@ -229,8 +229,8 @@ function readIfd(dataView, ifdType, tiffHeaderOffset, offset, byteOrder, include

if (Constants.USE_THUMBNAIL && (offset < dataView.byteLength - Types.getTypeSize('LONG'))) {
const nextIfdOffset = Types.getLongAt(dataView, offset, byteOrder);
if (nextIfdOffset !== 0) {
tags['Thumbnail'] = readIfd(dataView, ifdType, tiffHeaderOffset, tiffHeaderOffset + nextIfdOffset, byteOrder, true);
if (nextIfdOffset !== 0 && ifdType === IFD_TYPE_0TH) {
tags['Thumbnail'] = readIfd(dataView, IFD_TYPE_1ST, tiffHeaderOffset, tiffHeaderOffset + nextIfdOffset, byteOrder, true);
}
}

Expand Down
20 changes: 14 additions & 6 deletions test/unit/tags-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,17 @@ describe('tags', () => {
// Field count + field + offset to next IFD.
+ '\x00\x01' + '\x48\x12\x00\x01\x00\x00\x00\x01\x43\x00\x00\x00' + '\x00\x00\x00\x00'
);
Tags.__set__('TagNames', {'0th': {
0x4711: 'MyExifTag1',
0x4812: 'MyExifTag2'
}});
Tags.__set__(
'TagNames',
{
'0th': {
0x4711: 'MyExifTag1'
},
'1st': {
0x4812: 'MyExifTag2'
}
}
);

const tags = read0thIfd(dataView, 0, ByteOrder.BIG_ENDIAN);

Expand All @@ -249,8 +256,9 @@ describe('tags', () => {
});

it('should be able to read Exif IFD', () => {
// Padding + field count + field.
const dataView = getDataView('\x00\x00\x00\x00' + '\x00\x01' + '\x47\x11\x00\x01\x00\x00\x00\x01\x42\x00\x00\x00');
// Padding + field count + field + offset to next IFD + padding for fake IFD.
// Next IFD should be ignored when coming from other IFD than 0th.
const dataView = getDataView('\x00\x00\x00\x00' + '\x00\x01' + '\x47\x11\x00\x01\x00\x00\x00\x01\x42\x00\x00\x00' + '\x00\x00\x00\x04' + '\x01\x02');
let tags = {'Exif IFD Pointer': {value: 4}};
Tags.__set__('TagNames', {'exif': {0x4711: 'MyExifTag'}});
tags = readExifIfd(tags, dataView, 0, ByteOrder.BIG_ENDIAN);
Expand Down

0 comments on commit 6039ea5

Please sign in to comment.