Skip to content

Commit ed18b06

Browse files
committed
fix(webp): validate EXIF chunk bounds
Signed-off-by: Vlad (Kuzmin) Erium <libalias@gmail.com>
1 parent 165f18b commit ed18b06

4 files changed

Lines changed: 108 additions & 3 deletions

File tree

src/webp.imageio/webpinput.cpp

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,19 @@ OIIO_PLUGIN_NAMESPACE_BEGIN
1515
namespace webp_pvt {
1616

1717

18+
static bool
19+
webp_exif_payload_has_tiff_header(cspan<uint8_t> exif)
20+
{
21+
const size_t exif_header_size = 6;
22+
const bool has_exif_header = exif.size() >= exif_header_size
23+
&& exif[0] == 'E' && exif[1] == 'x'
24+
&& exif[2] == 'i' && exif[3] == 'f'
25+
&& exif[4] == 0 && exif[5] == 0;
26+
const size_t tiff_header_offset = has_exif_header ? exif_header_size : 0;
27+
return exif.size() >= tiff_header_offset + sizeof(TIFFHeader);
28+
}
29+
30+
1831
class WebpInput final : public ImageInput {
1932
public:
2033
WebpInput() {}
@@ -181,9 +194,9 @@ WebpInput::open(const std::string& name, ImageSpec& spec,
181194
WebPChunkIterator chunk_iter;
182195
if (m_demux_flags & EXIF_FLAG
183196
&& WebPDemuxGetChunk(m_demux, "EXIF", 1, &chunk_iter)) {
184-
decode_exif(string_view((const char*)chunk_iter.chunk.bytes + 6,
185-
chunk_iter.chunk.size - 6),
186-
m_spec);
197+
cspan<uint8_t> exif_span(chunk_iter.chunk.bytes, chunk_iter.chunk.size);
198+
if (webp_exif_payload_has_tiff_header(exif_span))
199+
decode_exif(exif_span, m_spec);
187200
WebPDemuxReleaseChunkIterator(&chunk_iter);
188201
}
189202
if (m_demux_flags & XMP_FLAG

testsuite/webp/ref/out-webp1.1.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,8 @@ rgba.webp : 64 x 64, 4 channel, uint8 webp
2424
channel list: R, G, B, A
2525
oiio:ColorSpace: "srgb_rec709_scene"
2626
oiio:UnassociatedAlpha: 1
27+
short-exif-len0.webp-ok
28+
short-exif-len4.webp-ok
29+
short-exif-len5.webp-ok
30+
short-exif-len6.webp-ok
31+
short-exif-len13.webp-ok

testsuite/webp/run.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,23 @@
1414

1515
command += oiiotool ("--create 64x64 4 -o rgba.webp")
1616
command += info_command ("rgba.webp", "--iconfig oiio:UnassociatedAlpha 1", safematch=True)
17+
18+
19+
def iconvert_to_null(filename, success_marker):
20+
output = filename.rsplit(".", 1)[0] + ".null"
21+
app = "(" + oiio_app("iconvert") + filename + " " + output
22+
app += " && echo " + success_marker + ")"
23+
return run_app(app)
24+
25+
26+
command += oiiotool ("--create 1x1 3 -d uint8 -o base-short-exif.webp")
27+
command += run_app (pythonbin + " src/make-short-exif-webp.py", silent=True)
28+
short_exif_files = [
29+
"short-exif-len0.webp",
30+
"short-exif-len4.webp",
31+
"short-exif-len5.webp",
32+
"short-exif-len6.webp",
33+
"short-exif-len13.webp",
34+
]
35+
for f in short_exif_files:
36+
command += iconvert_to_null(f, f + "-ok")
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
import struct
8+
9+
10+
BASE = "base-short-exif.webp"
11+
EXIF_FLAG = 0x08
12+
13+
CASES = [
14+
("short-exif-len0.webp", b""),
15+
("short-exif-len4.webp", b"Exif"),
16+
("short-exif-len5.webp", b"Exif\x00"),
17+
("short-exif-len6.webp", b"Exif\x00\x00"),
18+
("short-exif-len13.webp", b"Exif\x00\x00II*\x00\x08\x00\x00"),
19+
]
20+
21+
22+
def read_chunks(data):
23+
if len(data) < 12 or data[:4] != b"RIFF" or data[8:12] != b"WEBP":
24+
raise RuntimeError("%s is not a RIFF WebP file" % BASE)
25+
26+
chunks = []
27+
offset = 12
28+
while offset < len(data):
29+
if offset + 8 > len(data):
30+
raise RuntimeError("truncated WebP chunk header")
31+
fourcc = data[offset : offset + 4]
32+
size = struct.unpack_from("<I", data, offset + 4)[0]
33+
begin = offset + 8
34+
end = begin + size
35+
if end > len(data):
36+
raise RuntimeError("truncated WebP chunk payload")
37+
chunks.append((fourcc, data[begin:end]))
38+
offset = end + (size & 1)
39+
return chunks
40+
41+
42+
def write_chunk(fourcc, payload):
43+
chunk = fourcc + struct.pack("<I", len(payload)) + payload
44+
if len(payload) & 1:
45+
chunk += b"\x00"
46+
return chunk
47+
48+
49+
def make_webp(exif_payload, image_chunks):
50+
vp8x_payload = bytes((EXIF_FLAG, 0, 0, 0, 0, 0, 0, 0, 0, 0))
51+
chunks = write_chunk(b"VP8X", vp8x_payload)
52+
chunks += write_chunk(b"EXIF", exif_payload)
53+
for fourcc, payload in image_chunks:
54+
if fourcc not in (b"VP8X", b"EXIF"):
55+
chunks += write_chunk(fourcc, payload)
56+
body = b"WEBP" + chunks
57+
return b"RIFF" + struct.pack("<I", len(body)) + body
58+
59+
60+
with open(BASE, "rb") as input_file:
61+
base_data = input_file.read()
62+
63+
image_chunks = read_chunks(base_data)
64+
65+
for filename, exif_payload in CASES:
66+
with open(filename, "wb") as output_file:
67+
output_file.write(make_webp(exif_payload, image_chunks))

0 commit comments

Comments
 (0)