Skip to content

Commit 884dfd6

Browse files
authored
Fix EXIF bugs where corrupted exif blocks could overrun memory (#3627)
In one case, we actually had a check for this, but an assignment to an int made the nonsensical offset appear negative, and we only tested whether the necessary offset it was bigger than the buffer size. Keeping it as (unsigned) size_t makes the test work as intended. In another case, there were several places where we never checked that we were staying within the exif block, and here we address this by changing the utility decode_ifd so instead of passing it a pointer to the ifd, it passes the offset (the pointer turned out to always be inside the buffer) so it can check the extent for subsequent accesses. Also some fixes related to squashing undefined behavior sanitizer cases.
1 parent a37aa72 commit 884dfd6

11 files changed

Lines changed: 89 additions & 33 deletions

File tree

src/libOpenImageIO/exif.cpp

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ tiff_data_size(TIFFDataType tifftype)
157157
// Sizes of TIFFDataType members
158158
static size_t sizes[] = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8, 4 };
159159
const int num_data_sizes = sizeof(sizes) / sizeof(*sizes);
160-
int dir_index = (int)tifftype;
160+
int dir_index = bitcast<int>(tifftype);
161161
if (dir_index < 0 || dir_index >= num_data_sizes) {
162162
// Inform caller about corrupted entry.
163163
return -1;
@@ -360,9 +360,8 @@ makernote_handler(const TagInfo& /*taginfo*/, const TIFFDirEntry& dir,
360360
if (spec.get_string_attribute("Make") == "Canon") {
361361
std::vector<size_t> ifdoffsets { 0 };
362362
std::set<size_t> offsets_seen;
363-
decode_ifd((unsigned char*)buf.data() + dir.tdir_offset, buf, spec,
364-
pvt::canon_maker_tagmap_ref(), offsets_seen, swapendian,
365-
offset_adjustment);
363+
decode_ifd(buf, dir.tdir_offset, spec, pvt::canon_maker_tagmap_ref(),
364+
offsets_seen, swapendian, offset_adjustment);
366365
} else {
367366
// Maybe we just haven't parsed the Maker metadata yet?
368367
// Allow a second try later by just stashing the maker note offset.
@@ -668,8 +667,10 @@ add_exif_item_to_spec(ImageSpec& spec, const char* name,
668667
float* f = OIIO_ALLOCA(float, n);
669668
for (int i = 0; i < n; ++i) {
670669
unsigned int num, den;
671-
num = ((const unsigned int*)dataptr)[2 * i + 0];
672-
den = ((const unsigned int*)dataptr)[2 * i + 1]; //NOSONAR
670+
memcpy(&num, dataptr + (2 * i) * sizeof(unsigned int),
671+
sizeof(unsigned int));
672+
memcpy(&den, dataptr + (2 * i + 1) * sizeof(unsigned int),
673+
sizeof(unsigned int)); //NOSONAR
673674
if (swab) {
674675
swap_endian(&num);
675676
swap_endian(&den);
@@ -687,8 +688,9 @@ add_exif_item_to_spec(ImageSpec& spec, const char* name,
687688
float* f = OIIO_ALLOCA(float, n);
688689
for (int i = 0; i < n; ++i) {
689690
int num, den;
690-
num = ((const int*)dataptr)[2 * i + 0];
691-
den = ((const int*)dataptr)[2 * i + 1]; //NOSONAR
691+
memcpy(&num, dataptr + (2 * i) * sizeof(int), sizeof(int));
692+
memcpy(&den, dataptr + (2 * i + 1) * sizeof(int),
693+
sizeof(int)); //NOSONAR
692694
if (swab) {
693695
swap_endian(&num);
694696
swap_endian(&den);
@@ -767,7 +769,9 @@ read_exif_tag(ImageSpec& spec, const TIFFDirEntry* dirp, cspan<uint8_t> buf,
767769

768770
// Make a copy of the pointed-to TIFF directory, swab the components
769771
// if necessary.
770-
TIFFDirEntry dir = *dirp;
772+
TIFFDirEntry dir;
773+
memcpy(&dir, dirp, sizeof(TIFFDirEntry));
774+
unsigned int unswapped_tdir_offset = dir.tdir_offset;
771775
if (swab) {
772776
swap_endian(&dir.tdir_tag);
773777
swap_endian(&dir.tdir_type);
@@ -785,7 +789,7 @@ read_exif_tag(ImageSpec& spec, const TIFFDirEntry* dirp, cspan<uint8_t> buf,
785789
if (dir.tdir_tag == TIFFTAG_EXIFIFD || dir.tdir_tag == TIFFTAG_GPSIFD) {
786790
// Special case: It's a pointer to a private EXIF directory.
787791
// Handle the whole thing recursively.
788-
unsigned int offset = dirp->tdir_offset; // int stored in offset itself
792+
auto offset = unswapped_tdir_offset; // int stored in offset itself
789793
if (swab)
790794
swap_endian(&offset);
791795
if (offset >= size_t(buf.size())) {
@@ -840,7 +844,7 @@ read_exif_tag(ImageSpec& spec, const TIFFDirEntry* dirp, cspan<uint8_t> buf,
840844
} else if (dir.tdir_tag == TIFFTAG_INTEROPERABILITYIFD) {
841845
// Special case: It's a pointer to a private EXIF directory.
842846
// Handle the whole thing recursively.
843-
unsigned int offset = dirp->tdir_offset; // int stored in offset itself
847+
auto offset = unswapped_tdir_offset; // int stored in offset itself
844848
if (swab)
845849
swap_endian(&offset);
846850
if (offset >= size_t(buf.size())) {
@@ -1004,23 +1008,32 @@ encode_exif_entry(const ParamValue& p, int tag, std::vector<TIFFDirEntry>& dirs,
10041008

10051009

10061010

1007-
// Decode a raw Exif data block and save all the metadata in an
1008-
// ImageSpec. Return true if all is ok, false if the exif block was
1011+
// Decode a raw Exif data block and save all the metadata in an ImageSpec. The
1012+
// data is all inside buf. The ifd itself is at the position `ifd_offset`
1013+
// within the buf. Return true if all is ok, false if the exif block was
10091014
// somehow malformed.
1010-
void
1011-
pvt::decode_ifd(const unsigned char* ifd, cspan<uint8_t> buf, ImageSpec& spec,
1015+
bool
1016+
pvt::decode_ifd(cspan<uint8_t> buf, size_t ifd_offset, ImageSpec& spec,
10121017
const TagMap& tag_map, std::set<size_t>& ifd_offsets_seen,
10131018
bool swab, int offset_adjustment)
10141019
{
10151020
// Read the directory that the header pointed to. It should contain
10161021
// some number of directory entries containing tags to process.
1017-
unsigned short ndirs = *(const unsigned short*)ifd;
1022+
if (ifd_offset + 2 > std::size(buf)) {
1023+
return false; // asking us to read beyond the buffer
1024+
}
1025+
auto ifd = buf.data() + ifd_offset;
1026+
unsigned short ndirs = *(const unsigned short*)(buf.data() + ifd_offset);
10181027
if (swab)
10191028
swap_endian(&ndirs);
1029+
if (ifd_offset + 2 + ndirs * sizeof(TIFFDirEntry) > std::size(buf)) {
1030+
return false; // asking us to read beyond the buffer
1031+
}
10201032
for (int d = 0; d < ndirs; ++d)
10211033
read_exif_tag(spec,
10221034
(const TIFFDirEntry*)(ifd + 2 + d * sizeof(TIFFDirEntry)),
10231035
buf, swab, offset_adjustment, ifd_offsets_seen, tag_map);
1036+
return true;
10241037
}
10251038

10261039

@@ -1140,12 +1153,12 @@ decode_exif(cspan<uint8_t> exif, ImageSpec& spec)
11401153
if (swab)
11411154
swap_endian(&head.tiff_diroff);
11421155

1143-
const unsigned char* ifd = ((const unsigned char*)exif.data()
1144-
+ head.tiff_diroff);
11451156
// keep track of IFD offsets we've already seen to avoid infinite
11461157
// recursion if there are circular references.
11471158
std::set<size_t> ifd_offsets_seen;
1148-
decode_ifd(ifd, exif, spec, exif_tagmap_ref(), ifd_offsets_seen, swab);
1159+
if (!decode_ifd(exif, head.tiff_diroff, spec, exif_tagmap_ref(),
1160+
ifd_offsets_seen, swab))
1161+
return false;
11491162

11501163
// A few tidbits to look for
11511164
ParamValue* p;
@@ -1172,9 +1185,10 @@ decode_exif(cspan<uint8_t> exif, ImageSpec& spec)
11721185
int makernote_offset = spec.get_int_attribute("oiio:MakerNoteOffset");
11731186
if (makernote_offset > 0) {
11741187
if (Strutil::iequals(spec.get_string_attribute("Make"), "Canon")) {
1175-
decode_ifd((unsigned char*)exif.data() + makernote_offset, exif,
1176-
spec, pvt::canon_maker_tagmap_ref(), ifd_offsets_seen,
1177-
swab);
1188+
if (!decode_ifd(exif, makernote_offset, spec,
1189+
pvt::canon_maker_tagmap_ref(), ifd_offsets_seen,
1190+
swab))
1191+
return false;
11781192
}
11791193
// Now we can erase the attrib we used to pass the message about
11801194
// the maker note offset.

src/libOpenImageIO/exif.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ namespace pvt {
3131
inline const void*
3232
dataptr(const TIFFDirEntry& td, cspan<uint8_t> data, int offset_adjustment)
3333
{
34-
int len = tiff_data_size(td);
34+
size_t len = tiff_data_size(td);
3535
if (len <= 4)
3636
return (const char*)&td.tdir_offset;
3737
else {
3838
int offset = td.tdir_offset + offset_adjustment;
39-
if (offset < 0 || offset + len > (int)data.size())
39+
if (offset < 0 || size_t(offset) + len > std::size(data))
4040
return nullptr; // out of bounds!
4141
return (const char*)data.data() + offset;
4242
}
@@ -113,7 +113,7 @@ void append_tiff_dir_entry (std::vector<TIFFDirEntry> &dirs,
113113
size_t offset_override = 0,
114114
OIIO::endian endianreq = OIIO::endian::native);
115115

116-
void decode_ifd (const unsigned char *ifd, cspan<uint8_t> buf,
116+
bool decode_ifd (cspan<uint8_t> buf, size_t ifd_offset,
117117
ImageSpec &spec, const TagMap& tag_map,
118118
std::set<size_t>& ifd_offsets_seen, bool swab=false,
119119
int offset_adjustment=0);

src/psd.imageio/psdinput.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,8 +309,9 @@ class PSDInput final : public ImageInput {
309309
// Convert from photoshop native alpha to
310310
// associated/premultiplied
311311
template<class T>
312-
void removeBackground(T* data, int size, int nchannels, int alpha_channel,
313-
double* background)
312+
OIIO_NO_SANITIZE_UNDEFINED void
313+
removeBackground(T* data, int size, int nchannels, int alpha_channel,
314+
double* background)
314315
{
315316
// RGB = CompRGB - (1 - alpha) * Background;
316317
double scale = std::numeric_limits<T>::is_integer

testsuite/jpeg-corrupt/ref/out-alt.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,13 @@ src/corrupt-exif.jpg : 256 x 256, 3 channel, uint8 jpeg
88
YResolution: 300
99
jpeg:subsampling: "4:2:0"
1010
oiio:ColorSpace: "sRGB"
11+
Reading src/corrupt-exif-1626.jpg
12+
src/corrupt-exif-1626.jpg : 256 x 256, 3 channel, uint8 jpeg
13+
SHA-1: 6CBB7DB602A67DB300AB28842F20F6DCA02B82B1
14+
channel list: R, G, B
15+
Orientation: 1 (normal)
16+
ResolutionUnit: "in"
17+
XResolution: 300
18+
YResolution: 300
19+
jpeg:subsampling: "4:2:0"
20+
oiio:ColorSpace: "sRGB"

testsuite/jpeg-corrupt/ref/out.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,13 @@ src/corrupt-exif.jpg : 256 x 256, 3 channel, uint8 jpeg
88
YResolution: 300
99
jpeg:subsampling: "4:2:0"
1010
oiio:ColorSpace: "sRGB"
11+
Reading src/corrupt-exif-1626.jpg
12+
src/corrupt-exif-1626.jpg : 256 x 256, 3 channel, uint8 jpeg
13+
SHA-1: 6CBB7DB602A67DB300AB28842F20F6DCA02B82B1
14+
channel list: R, G, B
15+
Orientation: 1 (normal)
16+
ResolutionUnit: "in"
17+
XResolution: 300
18+
YResolution: 300
19+
jpeg:subsampling: "4:2:0"
20+
oiio:ColorSpace: "sRGB"

testsuite/jpeg-corrupt/run.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
#!/usr/bin/env python
22

3+
failureok = 1
4+
redirect = ' >> out.txt 2>&1 '
5+
36
# This file has a corrupted Exif block in the metadata. It used to
47
# crash on some platforms, on others would be caught by address sanitizer.
58
# Fixed by #1635. This test serves to guard against regressions.
69
command += info_command ("src/corrupt-exif.jpg", safematch=True)
710

8-
# Checking the error output is important for this test
9-
outputs = [ "out.txt" ]
10-
failureok = 1
11+
# This file has a corrupted Exif block that makes it look like one item has a
12+
# nonsensical length, that before being fixed, caused a buffer overrun.
13+
command += info_command ("src/corrupt-exif-1626.jpg", safematch=True)
14+
19.2 KB
Loading

testsuite/psd/ref/out.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,3 +1064,7 @@ src/layer-mask.psd : 10 x 10, 4 channel, uint8 psd
10641064
stEvt:instanceID: "xmp.iid:a64763c8-be7b-ff48-b857-0f1ee8e5da2b; xmp.iid:9847e4c5-ca7e-fa42-9c7f-5fe6373d31de; xmp.iid:ddf40b95-b12c-744e-a1a9-5e7724fe4ca9"
10651065
stEvt:softwareAgent: "Adobe Photoshop CC 2017 (Windows)"
10661066
stEvt:when: "2017-07-13T10:26:10+09:00; 2017-07-13T10:32:41+09:00; 2017-07-13T11:42:54+09:00"
1067+
oiiotool ERROR: read : Failed to decode Exif data
1068+
failed to open "src/crash-psd-exif-1632.psd": failed load_resources
1069+
Full command line was:
1070+
> oiiotool --info -v -a --hash src/crash-psd-exif-1632.psd

testsuite/psd/run.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/usr/bin/env python
22

3+
redirect = ' >> out.txt 2>&1 '
4+
35
files = [ "psd_123.psd", "psd_123_nomaxcompat.psd", "psd_bitmap.psd",
46
"psd_indexed_trans.psd", "psd_rgb_8.psd", "psd_rgb_16.psd",
57
"psd_rgb_32.psd", "psd_rgba_8.psd" ]
@@ -8,3 +10,6 @@
810

911
command += info_command ("src/different-mask-size.psd")
1012
command += info_command ("src/layer-mask.psd")
13+
14+
# This file has a corrupted Exif block
15+
command += info_command ("src/crash-psd-exif-1632.psd", failureok = 1)
24.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)