Skip to content

Commit 9ff6a7d

Browse files
committed
fix(sgi): Better detection of corrupt RLE info that could overflow (AcademySoftwareFoundation#5141)
* Change DASSERTs (which were correct, but did not enforce anything in release builds) into real checks that will return error messages and flags if the corruptions are found. * Switch to using spans for extra checks if anything goes wrong. * Use check_open. * Check that various header items are valid enum values. Signed-off-by: Larry Gritz <lg@larrygritz.com>
1 parent d465d68 commit 9ff6a7d

6 files changed

Lines changed: 76 additions & 20 deletions

File tree

src/include/OpenImageIO/dassert.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@
6060
#endif
6161

6262

63+
/// OIIO_CONTRACT_ASSERT is an OIIO 3.2 thing. To ease backporting patches
64+
/// that happen to use it, just define it for 3.0 as a synonym for
65+
/// OIIO_DASSERT.
66+
#define OIIO_CONTRACT_ASSERT(condition) OIIO_DASSERT(condition)
67+
68+
6369
/// Define OIIO_STATIC_ASSERT(cond) as a wrapper around static_assert(cond),
6470
/// with appropriate fallbacks for older C++ standards.
6571
#if (__cplusplus >= 201700L) /* FIXME - guess the token, fix when C++17 */

src/sgi.imageio/sgi_pvt.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ struct SgiHeader {
2424
int16_t magic; // must be 0xDA01 (big-endian)
2525
int8_t storage; // compression used, see StorageFormat enum
2626
int8_t bpc; // number of bytes per pixel channel
27-
uint16_t dimension; // dimension of he image, see Dimension
27+
uint16_t dimension; // dimension of the image, see Dimension
2828
uint16_t xsize; // width in pixels
2929
uint16_t ysize; // height in pixels
3030
uint16_t zsize; // number of channels: 1(B/W), 3(RGB) or 4(RGBA)

src/sgi.imageio/sgiinput.cpp

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,10 @@ class SgiInput final : public ImageInput {
4848
// Return true if ok, false if there was a read error.
4949
bool read_offset_tables();
5050

51-
// read channel scanline data from file, uncompress it and save the data to
52-
// 'out' buffer; 'out' should be allocate before call to this method.
53-
// Return true if ok, false if there was a read error.
51+
// read channel scanline data from file, uncompress it and save the data
52+
// to 'outbuf' buffer. Return true if ok, false if there was a read error.
5453
bool uncompress_rle_channel(int scanline_off, int scanline_len,
55-
unsigned char* out);
54+
span<unsigned char> outbf);
5655
};
5756

5857

@@ -159,6 +158,12 @@ SgiInput::open(const std::string& name, ImageSpec& spec)
159158
m_spec = ImageSpec(m_sgi_header.xsize, height, nchannels,
160159
m_sgi_header.bpc == 1 ? TypeDesc::UINT8
161160
: TypeDesc::UINT16);
161+
162+
if (!check_open(m_spec, { 0, 65535, 0, 65535, 0, 1, 0, 4 })) {
163+
close();
164+
return false;
165+
}
166+
162167
if (Strutil::safe_strlen(m_sgi_header.imagename,
163168
sizeof(m_sgi_header.imagename)))
164169
m_spec.attribute("ImageDescription", m_sgi_header.imagename);
@@ -198,8 +203,9 @@ SgiInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/,
198203
ptrdiff_t scanline_offset = start_tab[off];
199204
ptrdiff_t scanline_length = length_tab[off];
200205
channeldata[c].resize(m_spec.width * bpc);
201-
uncompress_rle_channel(scanline_offset, scanline_length,
202-
&(channeldata[c][0]));
206+
if (!uncompress_rle_channel(scanline_offset, scanline_length,
207+
make_span(channeldata[c])))
208+
return false;
203209
}
204210
} else {
205211
// non-RLE case -- just read directly into our channel data
@@ -240,16 +246,20 @@ SgiInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/,
240246

241247
bool
242248
SgiInput::uncompress_rle_channel(int scanline_off, int scanline_len,
243-
unsigned char* out)
249+
span<unsigned char> outbuf)
244250
{
245251
int bpc = m_sgi_header.bpc;
246-
std::unique_ptr<unsigned char[]> rle_scanline(
252+
std::unique_ptr<unsigned char[]> rle_scanline_alloc(
247253
new unsigned char[scanline_len]);
254+
auto rle_scanline = make_span(rle_scanline_alloc.get(), scanline_len);
248255
ioseek(scanline_off);
249-
if (!ioread(&rle_scanline[0], 1, scanline_len))
256+
if (!ioread(rle_scanline.data(), 1, scanline_len))
250257
return false;
258+
OIIO_CONTRACT_ASSERT(outbuf.size()
259+
>= size_t(m_spec.width * m_sgi_header.bpc));
251260
int limit = m_spec.width;
252261
int i = 0;
262+
int iout = 0;
253263
if (bpc == 1) {
254264
// 1 bit per channel
255265
while (i < scanline_len) {
@@ -262,17 +272,23 @@ SgiInput::uncompress_rle_channel(int scanline_off, int scanline_len,
262272
// If the high bit is set, we just copy the next 'count' values
263273
if (value & 0x80) {
264274
while (count--) {
265-
OIIO_DASSERT(i < scanline_len && limit > 0);
266-
*(out++) = rle_scanline[i++];
275+
if (i >= scanline_len || limit <= 0) {
276+
errorfmt("Corrupt RLE data");
277+
return false;
278+
}
279+
outbuf[iout++] = rle_scanline[i++];
267280
--limit;
268281
}
269282
}
270283
// If the high bit is zero, we copy the NEXT value, count times
271284
else {
272285
value = rle_scanline[i++];
273286
while (count--) {
274-
OIIO_DASSERT(limit > 0);
275-
*(out++) = value;
287+
if (limit <= 0) {
288+
errorfmt("Corrupt RLE data");
289+
return false;
290+
}
291+
outbuf[iout++] = value;
276292
--limit;
277293
}
278294
}
@@ -290,18 +306,24 @@ SgiInput::uncompress_rle_channel(int scanline_off, int scanline_len,
290306
// If the high bit is set, we just copy the next 'count' values
291307
if (value & 0x80) {
292308
while (count--) {
293-
OIIO_DASSERT(i + 1 < scanline_len && limit > 0);
294-
*(out++) = rle_scanline[i++];
295-
*(out++) = rle_scanline[i++];
309+
if ((i + 1) >= scanline_len || limit <= 0) {
310+
errorfmt("Corrupt RLE data");
311+
return false;
312+
}
313+
outbuf[iout++] = rle_scanline[i++];
314+
outbuf[iout++] = rle_scanline[i++];
296315
--limit;
297316
}
298317
}
299318
// If the high bit is zero, we copy the NEXT value, count times
300319
else {
301320
while (count--) {
302-
OIIO_DASSERT(limit > 0);
303-
*(out++) = rle_scanline[i];
304-
*(out++) = rle_scanline[i + 1];
321+
if (limit <= 0) {
322+
errorfmt("Corrupt RLE data");
323+
return false;
324+
}
325+
outbuf[iout++] = rle_scanline[i];
326+
outbuf[iout++] = rle_scanline[i + 1];
305327
--limit;
306328
}
307329
i += 2;
@@ -363,6 +385,23 @@ SgiInput::read_header()
363385
swap_endian(&m_sgi_header.pixmax);
364386
swap_endian(&m_sgi_header.colormap);
365387
}
388+
389+
if (m_sgi_header.magic != sgi_pvt::SGI_MAGIC) {
390+
errorfmt("\"{}\" is not a SGI file, magic number doesn't match",
391+
m_filename);
392+
return false;
393+
}
394+
395+
// Corruption checks
396+
if (m_sgi_header.storage < sgi_pvt::VERBATIM
397+
|| m_sgi_header.storage > sgi_pvt::RLE //
398+
|| m_sgi_header.bpc < 1 || m_sgi_header.bpc > 2
399+
|| m_sgi_header.dimension > sgi_pvt::MULTI_SCANLINE_MULTI_CHANNEL
400+
|| m_sgi_header.dimension < sgi_pvt::ONE_SCANLINE_ONE_CHANNEL) {
401+
errorfmt("Corrupt SGI header");
402+
return false;
403+
}
404+
366405
return true;
367406
}
368407

testsuite/sgi/ref/out.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,9 @@ ref/rle-16.sgi : 100 x 63, 4 channel, uint16 sgi
2828
ImageDescription: "...rnold_SGI_Texture_Crash_Bugreport_01\Default_Pass_Main.1.sgi"
2929
Comparing "ref/rle-16.sgi" and "rle-16.sgi"
3030
PASS
31+
src/broken01.sgi : 8 x 2, 1 channel, uint8 sgi
32+
SHA-1: Corrupt RLE data
33+
channel list: Y
34+
compression: "rle"
35+
iinfo ERROR: Could not read src/broken01.sgi:
36+
Corrupt RLE data

testsuite/sgi/run.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@
44
# SPDX-License-Identifier: Apache-2.0
55
# https://github.com/AcademySoftwareFoundation/OpenImageIO
66

7+
redirect = ' >> out.txt 2>&1 '
78

89
imagedir = "ref/"
910
files = [ "norle-8.sgi", "rle-8.sgi", "norle-16.sgi", "rle-16.sgi" ]
1011
for f in files:
1112
command = command + rw_command (imagedir, f)
13+
14+
# Regression testing of error handling and corrupt files
15+
command += info_command ("--stats src/broken01.sgi",
16+
info_program="iinfo", failureok=True)

testsuite/sgi/src/broken01.sgi

667 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)