diff --git a/CHANGES.md b/CHANGES.md index cc0e80c862..d43cf06936 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -34,6 +34,12 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 * *ImageCache/TextureSystem*: - *api/TS*: `IBA::make_texture()` now honors "maketx:threads" hint [#5014](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5014) (3.2.0.0, 3.1.10.0) * New global attribute queries via OIIO::getattribute(): + - `limits:resolution` (default: 1048576) is a new settable/queryable global + attribute that caps the maximum number of pixels along any single image + dimension. `ImageInput::check_open` rejects files exceeding it. This + complements `limits:imagesize_MB` to catch corrupt headers that are tiny + in one dimension but absurdly large in another, which can defeat the + total-pixel-memory check. * Miscellaneous API changes: - *api*: Versioned namespace to preserve ABI compatibility between minor releases [#4869](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4869) (3.2.0.0) - *fmath.h*: `degrees()` and `radians()` are now `constexpr`. [#5151](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5151) (3.2.0.1, 3.1.13.0) diff --git a/src/include/OpenImageIO/imageio.h b/src/include/OpenImageIO/imageio.h index 0f864b147b..ef318c7e00 100644 --- a/src/include/OpenImageIO/imageio.h +++ b/src/include/OpenImageIO/imageio.h @@ -2295,6 +2295,8 @@ class OIIO_API ImageInput { /// implied by `range`. /// * Whether the channel count is within the `"limits:channels"` OIIO /// attribute. + /// * Whether any single dimension (width, height, depth) is within the + /// `"limits:resolution"` OIIO attribute. /// * The total uncompressed pixel data size is expected to be within the /// `"limits:imagesize_MB"` OIIO attribute. /// * The full_{width,height,depth} are valid and within the range. @@ -3904,6 +3906,18 @@ OIIO_API std::string geterror(bool clear = true); /// you should raise this limit. Setting the limit to 0 means having no /// limit. /// +/// - `int limits:resolution` (1048576) +/// +/// When nonzero, the maximum number of pixels allowed along any single +/// dimension (width, height, or depth) of an image. Images whose headers +/// indicate a larger dimension might be assumed to be corrupted or +/// malicious files. This complements `limits:imagesize_MB` by catching a +/// header that is small in one dimension but absurdly large in another, +/// which could otherwise slip under the total-size limit. The default is +/// 1048576 (2^20). In situations when images with a larger single +/// dimension are expected to be encountered, you should raise this limit. +/// Setting the limit to 0 means having no limit. (Added in version 3.2.) +/// /// - `int log_times` (0) /// /// When the `"log_times"` attribute is nonzero, `ImageBufAlgo` functions diff --git a/src/include/imageio_pvt.h b/src/include/imageio_pvt.h index eb1aebb798..55cd0e0c64 100644 --- a/src/include/imageio_pvt.h +++ b/src/include/imageio_pvt.h @@ -46,6 +46,7 @@ extern int png_linear_premult; extern int enable_hwy; extern int limit_channels; extern int limit_imagesize_MB; +extern int limit_resolution; extern int imagebuf_print_uncaught_errors; extern int imagebuf_use_imagecache; extern int imageinput_strict; diff --git a/src/libOpenImageIO/imageinput.cpp b/src/libOpenImageIO/imageinput.cpp index e9b9b78a24..4df8a2cbdf 100644 --- a/src/libOpenImageIO/imageinput.cpp +++ b/src/libOpenImageIO/imageinput.cpp @@ -1577,6 +1577,23 @@ ImageInput::check_open(const ImageSpec& spec, ROI range, uint64_t /*flags*/) spec.nchannels, OIIO::pvt::limit_channels); return false; } + if (OIIO::pvt::limit_resolution + && (spec.width > OIIO::pvt::limit_resolution + || spec.height > OIIO::pvt::limit_resolution + || spec.depth > OIIO::pvt::limit_resolution)) { + if (spec.depth > 1) { + errorfmt( + "{} image dimension {}x{}x{} exceeds \"limits:resolution\" = {} for a single dimension. Possible corrupt input?\nIf you're sure this is a valid file, raise the OIIO global attribute \"limits:resolution\".", + format_name(), spec.width, spec.height, spec.depth, + OIIO::pvt::limit_resolution); + } else { + errorfmt( + "{} image dimension {}x{} exceeds \"limits:resolution\" = {} for a single dimension. Possible corrupt input?\nIf you're sure this is a valid file, raise the OIIO global attribute \"limits:resolution\".", + format_name(), spec.width, spec.height, + OIIO::pvt::limit_resolution); + } + return false; + } if (OIIO::pvt::limit_imagesize_MB && spec.image_bytes(true) > OIIO::pvt::limit_imagesize_MB * imagesize_t(1024 * 1024)) { diff --git a/src/libOpenImageIO/imageio.cpp b/src/libOpenImageIO/imageio.cpp index 265aee896a..761e3a8e7c 100644 --- a/src/libOpenImageIO/imageio.cpp +++ b/src/libOpenImageIO/imageio.cpp @@ -64,6 +64,7 @@ int enable_hwy = 0; // Not enabled at build time int limit_channels(1024); int limit_imagesize_MB(std::min(32 * 1024, int(Sysutil::physical_memory() >> 20))); +int limit_resolution(1 << 20); // max pixels along any single dimension int imageinput_strict(0); ustring font_searchpath(Sysutil::getenv("OPENIMAGEIO_FONTS")); ustring plugin_searchpath(OIIO_DEFAULT_PLUGIN_SEARCHPATH); @@ -431,6 +432,10 @@ attribute(string_view name, TypeDesc type, const void* val) limit_imagesize_MB = *(const int*)val; return true; } + if (name == "limits:resolution" && type == TypeInt) { + limit_resolution = *(const int*)val; + return true; + } if (name == "oiio:print_uncaught_errors" && type == TypeInt) { oiio_print_uncaught_errors = *(const int*)val; return true; @@ -625,6 +630,10 @@ getattribute(string_view name, TypeDesc type, void* val) *(int*)val = limit_imagesize_MB; return true; } + if (name == "limits:resolution" && type == TypeInt) { + *(int*)val = limit_resolution; + return true; + } if (name == "tiff:multithread" && type == TypeInt) { *(int*)val = tiff_multithread; return true; diff --git a/testsuite/oiiotool-control/ref/out.txt b/testsuite/oiiotool-control/ref/out.txt index c8fed296cf..5ed7d887bd 100644 --- a/testsuite/oiiotool-control/ref/out.txt +++ b/testsuite/oiiotool-control/ref/out.txt @@ -112,6 +112,7 @@ oiiotool ERROR: -set : Invalid variable name "3" Full command line was: > oiiotool -echo "This should make an error:" -set 3 5 Expr getattribute(limits:channels) = 1024 +Expr getattribute(limits:resolution) = 1.04858e+06 Testing if with true cond (expect output): inside if clause, i=42 done diff --git a/testsuite/oiiotool-control/run.py b/testsuite/oiiotool-control/run.py index c165df5fed..d1fb951021 100755 --- a/testsuite/oiiotool-control/run.py +++ b/testsuite/oiiotool-control/run.py @@ -141,6 +141,7 @@ # Test getattribute in an expression command += oiiotool ('-echo "Expr getattribute(\"limits:channels\") = {getattribute(\"limits:channels\")}"') +command += oiiotool ('-echo "Expr getattribute(\"limits:resolution\") = {getattribute(\"limits:resolution\")}"') # Test --if --else --endif command += oiiotool ('-echo "Testing if with true cond (expect output):" -set i 42 -if "{i}" -echo " inside if clause, i={i}" -endif -echo " done" -echo " "')