Skip to content

Commit 6a3d866

Browse files
committed
fix(jpeg): validate metadata marker bounds
Signed-off-by: Vlad (Kuzmin) Erium <libalias@gmail.com>
1 parent 165f18b commit 6a3d866

8 files changed

Lines changed: 92 additions & 6 deletions

File tree

src/jpeg.imageio/jpeginput.cpp

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,28 @@ OIIO_PLUGIN_EXPORTS_END
5252
static const uint8_t JPEG_MAGIC1 = 0xff;
5353
static const uint8_t JPEG_MAGIC2 = 0xd8;
5454

55+
static const char exif_marker_prefix[] = "Exif\0";
56+
57+
static const char icc_marker_prefix[] = "ICC_PROFILE";
58+
59+
static bool
60+
is_exif_marker(jpeg_saved_marker_ptr marker)
61+
{
62+
return marker->marker == (JPEG_APP0 + 1)
63+
&& marker->data_length >= sizeof(exif_marker_prefix)
64+
&& !memcmp(marker->data, exif_marker_prefix,
65+
sizeof(exif_marker_prefix));
66+
}
67+
68+
static bool
69+
is_icc_profile_marker(jpeg_saved_marker_ptr marker)
70+
{
71+
return marker->marker == (JPEG_APP0 + 2)
72+
&& marker->data_length >= ICC_HEADER_SIZE
73+
&& !memcmp(marker->data, icc_marker_prefix,
74+
sizeof(icc_marker_prefix));
75+
}
76+
5577

5678
// For explanations of the error handling, see the "example.c" in the
5779
// libjpeg distribution.
@@ -271,8 +293,7 @@ JpgInput::open(const std::string& name, ImageSpec& newspec)
271293
m_spec.attribute(JPEG_SUBSAMPLING_ATTR, subsampling);
272294

273295
for (jpeg_saved_marker_ptr m = m_cinfo.marker_list; m; m = m->next) {
274-
if (m->marker == (JPEG_APP0 + 1) && m->data_length >= 4
275-
&& !strncmp((const char*)m->data, "Exif", 4)) {
296+
if (is_exif_marker(m)) {
276297
// The block starts with "Exif\0\0", so skip 6 bytes to get
277298
// to the start of the actual Exif data TIFF directory
278299
decode_exif(string_view((char*)m->data + 6, m->data_length - 6),
@@ -394,8 +415,7 @@ JpgInput::read_icc_profile(j_decompress_ptr cinfo, ImageSpec& spec)
394415
memset(marker_present, 0, (MAX_SEQ_NO + 1));
395416

396417
for (jpeg_saved_marker_ptr m = cinfo->marker_list; m; m = m->next) {
397-
if (m->marker == (JPEG_APP0 + 2)
398-
&& !strcmp((const char*)m->data, "ICC_PROFILE")) {
418+
if (is_icc_profile_marker(m)) {
399419
if (num_markers == 0)
400420
num_markers = GETJOCTET(m->data[13]);
401421
else if (num_markers != GETJOCTET(m->data[13]))
@@ -427,8 +447,7 @@ JpgInput::read_icc_profile(j_decompress_ptr cinfo, ImageSpec& spec)
427447

428448
// and fill it in
429449
for (jpeg_saved_marker_ptr m = cinfo->marker_list; m; m = m->next) {
430-
if (m->marker == (JPEG_APP0 + 2)
431-
&& !strcmp((const char*)m->data, "ICC_PROFILE")) {
450+
if (is_icc_profile_marker(m)) {
432451
int seq_no = GETJOCTET(m->data[12]);
433452
if (data_offset[seq_no] + data_length[seq_no] > icc_buf.size()) {
434453
errorfmt("Possible corrupt file, invalid ICC profile\n");

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ src/corrupt-icc-4552.jpg : 1500 x 1000, 3 channel, uint8 jpeg
4444
ICCProfile:rendering_intent: "Unknown"
4545
jpeg:subsampling: "4:2:0"
4646
oiio:ColorSpace: "srgb_rec709_scene"
47+
short-exif-app1-len4-ok
48+
short-exif-app1-len5-ok
49+
short-icc-app2-len11-ok
50+
short-icc-app2-len12-ok
51+
short-icc-app2-len13-ok
4752
oiiotool ERROR: read : JPEG error: Corrupt JPEG data: 256 extraneous bytes before marker 0xdb ("src/corrupt-iptc-8011.jpg")
4853
Corrupted IPTC data
4954
Full command line was:

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ src/corrupt-icc-4552.jpg : 1500 x 1000, 3 channel, uint8 jpeg
4343
ICCProfile:rendering_intent: "Unknown"
4444
jpeg:subsampling: "4:2:0"
4545
oiio:ColorSpace: "srgb_rec709_scene"
46+
short-exif-app1-len4-ok
47+
short-exif-app1-len5-ok
48+
short-icc-app2-len11-ok
49+
short-icc-app2-len12-ok
50+
short-icc-app2-len13-ok
4651
oiiotool ERROR: read : JPEG error: Corrupt JPEG data: 256 extraneous bytes before marker 0xdb ("src/corrupt-iptc-8011.jpg")
4752
Corrupted IPTC data
4853
Full command line was:

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ src/corrupt-icc-4552.jpg : 1500 x 1000, 3 channel, uint8 jpeg
4444
ICCProfile:rendering_intent: "Unknown"
4545
jpeg:subsampling: "4:2:0"
4646
oiio:ColorSpace: "srgb_rec709_scene"
47+
short-exif-app1-len4-ok
48+
short-exif-app1-len5-ok
49+
short-icc-app2-len11-ok
50+
short-icc-app2-len12-ok
51+
short-icc-app2-len13-ok
4752
oiiotool ERROR: read : JPEG error: Corrupt JPEG data: 256 extraneous bytes before marker 0xdb ("src/corrupt-iptc-8011.jpg")
4853
Corrupted IPTC data
4954
Full command line was:

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ src/corrupt-icc-4552.jpg : 1500 x 1000, 3 channel, uint8 jpeg
4646
ICCProfile:rendering_intent: "Unknown"
4747
jpeg:subsampling: "4:2:0"
4848
oiio:ColorSpace: "srgb_rec709_scene"
49+
short-exif-app1-len4-ok
50+
short-exif-app1-len5-ok
51+
short-icc-app2-len11-ok
52+
short-icc-app2-len12-ok
53+
short-icc-app2-len13-ok
4954
oiiotool ERROR: read : JPEG error: Corrupt JPEG data: 256 extraneous bytes before marker 0xdb ("src/corrupt-iptc-8011.jpg")
5055
Corrupted IPTC data
5156
Full command line was:

testsuite/jpeg-corrupt/ref/out.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ src/corrupt-icc-4552.jpg : 1500 x 1000, 3 channel, uint8 jpeg
4444
ICCProfile:rendering_intent: "Unknown"
4545
jpeg:subsampling: "4:2:0"
4646
oiio:ColorSpace: "srgb_rec709_scene"
47+
short-exif-app1-len4-ok
48+
short-exif-app1-len5-ok
49+
short-icc-app2-len11-ok
50+
short-icc-app2-len12-ok
51+
short-icc-app2-len13-ok
4752
oiiotool ERROR: read : JPEG error: Corrupt JPEG data: 256 extraneous bytes before marker 0xdb ("src/corrupt-iptc-8011.jpg")
4853
Corrupted IPTC data
4954
Full command line was:

testsuite/jpeg-corrupt/run.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
failureok = 1
99
redirect = ' >> out.txt 2>&1 '
1010

11+
command += oiiotool("--create 1x1 3 -d uint8 -o base-short-marker.jpg")
12+
command += run_app(pythonbin + " src/make-short-marker-jpegs.py", silent=True)
13+
1114
# This file has a corrupted Exif block in the metadata. It used to
1215
# crash on some platforms, on others would be caught by address sanitizer.
1316
# Fixed by #1635. This test serves to guard against regressions.
@@ -25,5 +28,13 @@
2528
# This file has a corrupted ICC profile block
2629
command += info_command ("src/corrupt-icc-4552.jpg", safematch=True)
2730

31+
# These files have short APP1/APP2 metadata marker payloads that used to be
32+
# read past their saved-marker buffers before being ignored.
33+
command += run_app(oiiotool("short-exif-app1-len4.jpg --echo short-exif-app1-len4-ok"))
34+
command += run_app(oiiotool("short-exif-app1-len5.jpg --echo short-exif-app1-len5-ok"))
35+
command += run_app(oiiotool("short-icc-app2-len11.jpg --echo short-icc-app2-len11-ok"))
36+
command += run_app(oiiotool("short-icc-app2-len12.jpg --echo short-icc-app2-len12-ok"))
37+
command += run_app(oiiotool("short-icc-app2-len13.jpg --echo short-icc-app2-len13-ok"))
38+
2839
# This file had corrupted IPTC data
2940
command += oiiotool("-oiioattrib imageinput:strict 1 -info -v src/corrupt-iptc-8011.jpg")
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env python
2+
3+
# Copyright Contributors to the OpenImageIO project.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# https://github.com/AcademySoftwareFoundation/OpenImageIO
6+
7+
from pathlib import Path
8+
9+
10+
APP1 = 0xE1
11+
APP2 = 0xE2
12+
BASE = Path("base-short-marker.jpg")
13+
14+
15+
def marker(marker_id, payload):
16+
length = len(payload) + 2
17+
return b"\xff" + bytes([marker_id]) + length.to_bytes(2, "big") + payload
18+
19+
20+
def write_with_marker(name, marker_id, payload):
21+
data = BASE.read_bytes()
22+
if data[:2] != b"\xff\xd8":
23+
raise RuntimeError(f"{BASE} is not a JPEG stream")
24+
Path(name).write_bytes(data[:2] + marker(marker_id, payload) + data[2:])
25+
26+
27+
write_with_marker("short-exif-app1-len4.jpg", APP1, b"Exif")
28+
write_with_marker("short-exif-app1-len5.jpg", APP1, b"Exif\0")
29+
write_with_marker("short-icc-app2-len11.jpg", APP2, b"ICC_PROFILE")
30+
write_with_marker("short-icc-app2-len12.jpg", APP2, b"ICC_PROFILE\0")
31+
write_with_marker("short-icc-app2-len13.jpg", APP2, b"ICC_PROFILE\0\1")

0 commit comments

Comments
 (0)