Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/build-scripts/ci-test.bash
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ echo "Default timeout ${CTEST_TEST_TIMEOUT}"
echo "Test exclusions '${CTEST_EXCLUSIONS}'"
echo "CTEST_ARGS '${CTEST_ARGS}'"

# Adjustments to OPENIMAGEIO_OPTIONS
# - limits:imagesize_MB=32768 forces the same limit regardless of physical
# memory size of the CI runner, for the sake of uniform error messages.
export OPENIMAGEIO_OPTIONS="limits:imagesize_MB=32768,${OPENIMAGEIO_OPTIONS}"

pushd build
time ctest -C ${CMAKE_BUILD_TYPE} --force-new-ctest-process --output-on-failure \
-E "${CTEST_EXCLUSIONS}" --timeout ${CTEST_TEST_TIMEOUT} ${CTEST_ARGS}
Expand Down
5 changes: 4 additions & 1 deletion src/cineon.imageio/cineoninput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "libcineon/Cineon.h"

#include <OpenImageIO/dassert.h>
#include <OpenImageIO/filesystem.h>
#include <OpenImageIO/imageio.h>
#include <OpenImageIO/strutil.h>
#include <OpenImageIO/typedesc.h>
Expand Down Expand Up @@ -130,7 +131,9 @@ CineonInput::open(const std::string& name, ImageSpec& newspec)
m_spec = ImageSpec(m_cin.header.Width(), m_cin.header.Height(), nchannels,
typedesc);

if (!check_open(m_spec, { 0, 1 << 30, 0, 1 << 30, 0, 1, 0, 8 })) {
if (!check_open(m_spec, { 0, 1 << 30, 0, 1 << 30, 0, 1, 0, 8 })
|| !check_compression_ratio(m_spec, Filesystem::file_size(name))) {
close();
return false;
}

Expand Down
5 changes: 5 additions & 0 deletions src/cmake/testing.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,9 @@ macro (oiio_add_all_tests)
# Regression test (compiles its own helper and generates its own image)
# for a partial edge-tile heap overflow in the OpenEXR readers.
oiio_add_tests (openexr-partialtile)
# Self-contained decompression-bomb regression (ships its own tiny fixture);
# exercises both the C++ and C-API readers via the openexr:core attribute.
oiio_add_tests (openexr-bomb)
# if (NOT DEFINED ENV{${PROJECT_NAME}_CI})
# oiio_add_tests (openexr-damaged
# IMAGEDIR openexr-images
Expand All @@ -446,6 +449,8 @@ macro (oiio_add_all_tests)
oiio_add_tests (png png-damaged
ENABLEVAR ENABLE_PNG
IMAGEDIR oiio-images/png)
# Self-contained decompression-bomb regression (ships its own tiny fixture).
oiio_add_tests (png-bomb ENABLEVAR ENABLE_PNG)
oiio_add_tests (pnm
ENABLEVAR ENABLE_PNM
IMAGEDIR oiio-images)
Expand Down
7 changes: 7 additions & 0 deletions src/dds.imageio/ddsinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,13 @@ DDSInput::seek_subimage(int subimage, int miplevel)
return false;
}

// Decompression-bomb guard: reject a tiny file that declares a huge
// decoded image before internal_readimg() commits to the m_buf allocation.
// For compressed formats the declared bytes are the decoded (RGBA/half)
// size; genuine BCn ratios are well under the default cap.
if (!check_compression_ratio(m_spec, ioproxy()->size()))
return false;

m_subimage = subimage;
m_miplevel = miplevel;
return true;
Expand Down
3 changes: 2 additions & 1 deletion src/dpx.imageio/dpxinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,8 @@ DPXInput::seek_subimage(int subimage, int miplevel)
m_spec = ImageSpec(m_dpx.header.Width(), m_dpx.header.Height(),
m_dpx.header.ImageElementComponentCount(subimage),
typedesc);
if (!check_open(m_spec, { 0, 1 << 30, 0, 1 << 30, 0, 1 << 16, 0, 8 }))
if (!check_open(m_spec, { 0, 1 << 30, 0, 1 << 30, 0, 1 << 16, 0, 8 })
|| !check_compression_ratio(m_spec, m_filesize))
return false;

// xOffset/yOffset are defined as unsigned 32-bit integers, but m_spec.x/y are signed
Expand Down
13 changes: 13 additions & 0 deletions src/fits.imageio/fitsinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,19 @@ FitsInput::set_spec_info()
m_spec.set_format(TypeDesc::FLOAT);
else if (m_bitpix == -64)
m_spec.set_format(TypeDesc::DOUBLE);

// Validate dimensions and implied size for non-empty images. FITS permits
// an empty (0x0) primary HDU, which we pass through as metadata-only.
if (m_spec.width > 0 && m_spec.height > 0) {
if (m_spec.format == TypeDesc::UNKNOWN) {
errorfmt("Unsupported FITS BITPIX value {}", m_bitpix);
return false;
}
if (!check_open(m_spec, { 0, 1 << 20, 0, 1 << 20, 0, 1 << 16, 0, 4 }))
return false;
if (!check_compression_ratio(m_spec, Filesystem::file_size(m_filename)))
return false;
}
return true;
}

Expand Down
33 changes: 27 additions & 6 deletions src/fuzz/populate_corpora.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def _find_images_root() -> Path:
("../bmpsuite", ["bmp", "BMP"]),
],
"cineon": [
("testsuite/cineon/src", ["cin"]),
("../oiio-images/cineon", ["cin"]),
("../oiio-images", ["cin"]),
],
Expand All @@ -68,25 +69,32 @@ def _find_images_root() -> Path:
("testsuite/dicom/src", ["dcm"]),
],
"dpx": [
("testsuite/dpx/src", ["dpx"]),
("../oiio-images/dpx", ["dpx"]),
("../dpx-images-spi", ["dpx"]),
],
"openexr": [
("testsuite/openexr-bomb/src", ["exr"]),
("../oiio-images", ["exr"]),
("testsuite/oiio-images", ["exr"]),
],
"ffmpeg": [
("testsuite/ffmpeg/ref", ["mkv", "mov", "mp4", "avi"]),
("testsuite/ffmpeg/src", ["mkv", "mov", "mp4", "avi"]),
],
"fits": [
("testsuite/fits/src", ["fits", "fit"]),
("../fits-images/ftt4b", ["fits", "fit"]),
("../fits-images/pg93", ["fits", "fit"]),
],
"gif": [
("testsuite/gif/src", ["gif"]),
("../oiio-images/gif", ["gif"]),
],
# hdr: synthetic seed committed; no further sources needed
"hdr": [],
"hdr": [
("testsuite/hdr/src", ["hdr", "rgbe"]),
],
"heif": [
("../oiio-images/heif", ["heif", "heic", "avif"]),
("../heif-images", ["heif", "heic", "avif"]),
Expand All @@ -96,22 +104,30 @@ def _find_images_root() -> Path:
("../oiio-images/ico", ["ico"]),
],
# iff: synthetic seed committed; no further sources needed
"iff": [],
"iff": [
("testsuite/iff/src", ["iff", "z"]),
],
"jpeg": [
("testsuite/jpeg-corrupt/src", ["jpg", "jpeg"]),
("../oiio-images/jpeg", ["jpg", "jpeg"]),
("testsuite/jpeg/src", ["jpg", "jpeg"]),
("../oiio-images", ["jpg", "jpeg"]),
],
"jpeg2000": [
("testsuite/htj2k/src", ["j2c"]),
("../oiio-images/jpeg2000", ["jp2", "j2k"]),
("../j2kp4files_v1_5/codestreams_profile0", ["j2k"]),
],
# jpegxl: synthetic seed committed; no further sources needed
"jpegxl": [],
"jpegxl": [
("testsuite/jxl/src", ["jxl"]),
],
"openvdb": [
("testsuite/openvdb/src", ["vdb"]),
("testsuite/openvdb-damaged/src", ["vdb"]),
],
"png": [
("testsuite/png-bomb/src", ["png"]),
("testsuite/png-damaged/src", ["png"]),
("../oiio-images/png", ["png"]),
],
"pnm": [
Expand All @@ -126,15 +142,18 @@ def _find_images_root() -> Path:
("testsuite/ptex/src", ["ptx", "ptex"]),
],
"raw": [
("testsuite/raw/src", ["dng"]),
("../oiio-images/raw", ["cr2", "nef", "arw", "raf", "rw2", "orf",
"CR2", "NEF", "ARW", "RAF", "RW2", "ORF"]),
],
"rla": [
("testsuite/rla/src", ["rla"]),
("../oiio-images/rla", ["rla"]),
],
# sgi: synthetic seed committed; no further sources needed
"sgi": [],
"sgi": [
("testsuite/sgi/src", ["sgi", "rgb", "rgba", "bw", "int", "inta"]),
("../oiio-images", ["sgi", "rgb"]),
],
"softimage": [
("testsuite/softimage/src", ["pic"]),
("../oiio-images/softimage", ["pic"]),
Expand All @@ -149,10 +168,12 @@ def _find_images_root() -> Path:
("testsuite/tiff-suite/src", ["tif", "tiff"]),
],
"webp": [
("testsuite/webp/src", ["webp"]),
("../oiio-images/webp", ["webp"]),
],
# zfile: use the reference output produced by the testsuite (a valid zfile)
"zfile": [
("testsuite/zfile/src", ["zfile"]),
("testsuite/zfile/ref", ["zfile"]),
],
}
Expand Down
3 changes: 2 additions & 1 deletion src/gif.imageio/gifinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,8 @@ GIFInput::seek_subimage(int subimage, int miplevel)
m_spec.full_width = m_spec.width;
m_spec.full_depth = m_spec.depth;

if (!check_open(m_spec, { 0, 32768, 0, 32768, 0, 1, 0, 4 })) {
if (!check_open(m_spec, { 0, 32768, 0, 32768, 0, 1, 0, 4 })
|| !check_compression_ratio(m_spec, ioproxy()->size())) {
return false;
}

Expand Down
3 changes: 2 additions & 1 deletion src/hdr.imageio/hdrinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,8 @@ HdrInput::open(const std::string& name, ImageSpec& newspec)
m_spec.full_width = m_spec.width;
m_spec.full_height = m_spec.height;
// Validation of resolution
if (!check_open(m_spec, { 0, 65535, 0, 65535, 0, 1, 0, 4 })) {
if (!check_open(m_spec, { 0, 65535, 0, 65535, 0, 1, 0, 4 })
|| !check_compression_ratio(m_spec, ioproxy()->size())) {
close();
return false;
}
Expand Down
11 changes: 11 additions & 0 deletions src/heif.imageio/heifinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,17 @@ HeifInput::seek_subimage(int subimage, int miplevel)
: heif_channel_interleaved;
const int nchannels = is_monochrome ? 1 : m_has_alpha ? 4 : 3;

// Validate the handle's declared dimensions against OIIO's limits BEFORE
// asking libheif to decode (and allocate) the full image. libheif has its
// own internal security limits, but this additionally enforces OIIO's
// limits:* policy and rejects degenerate (zero/negative) dimensions.
m_spec = ImageSpec(m_ihandle.get_width(), m_ihandle.get_height(), nchannels,
(m_bitdepth > 8) ? TypeUInt16 : TypeUInt8);
if (!check_open(m_spec, { 0, 1 << 18, 0, 1 << 18, 0, 1, 0, 4 })) {
m_ctx.reset();
return false;
}

#if 0
try {
m_himage = m_ihandle.decode_image(heif_colorspace_RGB, chroma);
Expand Down
3 changes: 2 additions & 1 deletion src/ico.imageio/icoinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,8 @@ ICOInput::seek_subimage(int subimage, int miplevel)
bool ok = PNG_pvt::read_info(m_png, m_info, m_bpp, m_color_type,
m_interlace_type, m_bg, m_spec, true);
if (!ok || m_err
|| !check_open(m_spec, { 0, 1 << 30, 0, 1 << 30, 0, 1, 0, 4 })) {
|| !check_open(m_spec, { 0, 1 << 30, 0, 1 << 30, 0, 1, 0, 4 })
|| !check_compression_ratio(m_spec, ioproxy()->size())) {
return false;
}

Expand Down
7 changes: 3 additions & 4 deletions src/iff.imageio/iffinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,10 +283,9 @@ IffInput::open(const std::string& name, ImageSpec& spec)
// we save this position - it will be helpful in read_native_tile
m_tbmp_start = m_header.tbmp_start;

// Validity check resolutions. Width and height are a uint16, but this is
// an old format, we are guessing that there are no Maya IFF files that
// are > 64k pixels per side.
if (!check_open(m_spec, { 0, 1 << 16, 0, 1 << 16, 0, 1, 0, 5 }))
// Validity check resolutions and check for decompression bombs.
if (!check_open(m_spec, { 0, 1 << 16, 0, 1 << 16, 0, 1, 0, 5 })
|| !check_compression_ratio(m_spec, ioproxy()->size()))
return false;

spec = m_spec;
Expand Down
7 changes: 7 additions & 0 deletions src/jpeg.imageio/jpeginput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,16 @@ JpgInput::open(const std::string& name, ImageSpec& newspec)
m_spec = ImageSpec(m_cinfo.output_width, m_cinfo.output_height, nchannels,
TypeDesc::UINT8);

// Validity check resolutions.
if (!check_open(m_spec, { 0, 1 << 16, 0, 1 << 16, 0, 1, 0, 3 }))
return false;

// check_open's size cap still admits dimensions that are absurd for a tiny
// file, so also bound the declared-vs-compressed ratio.
imagesize_t filesize = m_io ? m_io->size() : Filesystem::file_size(name);
if (!check_compression_ratio(m_spec, filesize))
return false;

// Assume JPEG is in sRGB unless the Exif or XMP tags say otherwise.
m_spec.set_colorspace("srgb_rec709_scene");

Expand Down
19 changes: 19 additions & 0 deletions src/libOpenImageIO/imageinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1725,6 +1725,25 @@ ImageInput::check_open(const ImageSpec& spec, ROI range, uint64_t /*flags*/)
return false;
}

// Check for sensible tile sizes. A tile dimension of 0 means "not
// tiled", which is always fine; only reject negative sizes and tile
// dimensions that exceed the same per-format ceiling already applied
// to the image resolution above.
if (spec.tile_width < 0 || spec.tile_height < 0 || spec.tile_depth < 0) {
errorfmt(
"{} tile size may not be negative, but was {}x{}x{}. Possible corrupt input?",
format_name(), spec.tile_width, spec.tile_height, spec.tile_depth);
return false;
}
if (spec.tile_width > range.width() || spec.tile_height > range.height()
|| spec.tile_depth > range.depth()) {
errorfmt(
"{} tile size may not exceed {}x{}x{}, but was {}x{}x{}. Possible corrupt input?",
format_name(), range.width(), range.height(), range.depth(),
spec.tile_width, spec.tile_height, spec.tile_depth);
return false;
}

// Check for invalid full_* values for sensibility
if (spec.full_width == 0 && spec.full_height == 0 && spec.full_depth == 0
&& supports("noimage")) {
Expand Down
8 changes: 8 additions & 0 deletions src/null.imageio/nullimageio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,14 @@ NullInput::open(const std::string& name, ImageSpec& newspec,
}
}

// The query arguments above are parsed input, even though no file is
// ever read. Without this, "foo.null?CHANNELS=-1" sign-extended its way
// into a std::length_error that nothing catches, "RES=-4x-4" handed the
// caller a negative-sized spec, and a large enough CHANNELS hung building
// channel names. check_open() also rejects a negative or oversized TILE.
if (!check_open(m_topspec, ROI(0, 1 << 20, 0, 1 << 20, 0, 1 << 20, 0, 1024)))
return false;

m_value.resize(m_topspec.pixel_bytes()); // default fills with 0's
if (fvalue.size()) {
// Convert float to the native type
Expand Down
7 changes: 7 additions & 0 deletions src/openexr.imageio/exrinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1137,6 +1137,13 @@ OpenEXRInput::seek_subimage(int subimage, int miplevel)
if (!check_open(m_spec, { 0, 1 << 30, 0, 1 << 30, 0, 1, 0, 1 << 12 }))
return false;

// check_open's size cap still admits a dataWindow that is absurd for a tiny
// compressed file, so also bound the declared-vs-compressed ratio.
imagesize_t filesize = m_io ? m_io->size()
: Filesystem::file_size(m_filename);
if (!check_compression_ratio(m_spec, filesize))
return false;

if (miplevel == 0 && part.levelmode == Imf::ONE_LEVEL) {
return true;
}
Expand Down
7 changes: 7 additions & 0 deletions src/openexr.imageio/exrinput_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1121,6 +1121,13 @@ OpenEXRCoreInput::seek_subimage(int subimage, int miplevel)
if (!check_open(m_spec, { 0, 1 << 30, 0, 1 << 30, 0, 1, 0, 1 << 12 }))
return false;

// check_open's size cap still admits a dataWindow that is absurd for a tiny
// compressed file, so also bound the declared-vs-compressed ratio.
imagesize_t filesize = m_userdata.m_io ? m_userdata.m_io->size()
: Filesystem::file_size(m_filename);
if (!check_compression_ratio(m_spec, filesize))
return false;

if (miplevel == 0 && part.levelmode == EXR_TILE_ONE_LEVEL) {
return true;
}
Expand Down
9 changes: 9 additions & 0 deletions src/png.imageio/pnginput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,15 @@ PNGInput::open(const std::string& name, ImageSpec& newspec)
return false;
}

// check_open's size cap still admits dimensions that are absurd for a tiny
// compressed file, so also bound the declared-vs-compressed ratio.
imagesize_t filesize = ioproxy() ? ioproxy()->size()
: Filesystem::file_size(m_filename);
if (!check_compression_ratio(m_spec, filesize)) {
close();
return false;
}

string_view colorspace = m_spec.get_string_attribute("oiio:ColorSpace",
"srgb_rec709_scene");
const ColorConfig& colorconfig(ColorConfig::default_colorconfig());
Expand Down
7 changes: 7 additions & 0 deletions src/pnm.imageio/pnminput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,13 @@ PNMInput::open(const std::string& name, ImageSpec& newspec)
if (!check_open(m_spec)) // check for apparently invalid values
return false;

// Reject a tiny file that declares a huge image before the caller
// allocates the full (declared) pixel buffer. Binary PNM data must be
// present in the file and ASCII values cost >=2 bytes each, so a
// legitimate file never has an extreme ratio.
if (!check_compression_ratio(m_spec, m_io->size()))
return false;

m_remaining = append_remainder_to_buffer(m_file_contents, m_io,
m_remaining);
m_after_header = m_remaining;
Expand Down
5 changes: 5 additions & 0 deletions src/rla.imageio/rlainput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,11 @@ RLAInput::seek_subimage(int subimage, int miplevel)
3 + 3 + 256 }))
return false;

// Check for uncompressed size that is wildly out of proportion to
// file size.
if (!check_compression_ratio(m_spec, ioproxy()->size()))
return false;

// set channel formats and stride
int z_channel = -1;
m_stride = 0;
Expand Down
Loading
Loading