From b29316e15d2200b99200f8290c036a2e8d3d7ffe Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 27 Jun 2026 22:08:18 -0400 Subject: [PATCH] feat: add "limits:resolution" global attribute to cap per-dimension size The existing "limits:imagesize_MB" rejects files whose total uncompressed size exceeds a cap, but it cannot catch an image that is tiny in one dimension yet absurdly large in another (e.g. an image claiming 32 x 50331680, which slips under the 32 GB default). Add a companion global attribute, "limits:resolution", that caps the maximum number of pixels along any single dimension. Like imagesize_MB, this is enforced only in ImageInput::check_open(), intended for validity checks when reading untrusted files. Applications may set the attribute larger or smaller than this default, depending on their need to deal with images larger than these limits, and/or willingness to risk mistaking a corrupted or maliciously crafted image for a legitimate one on this basis. Assisted-by: Claude Code / Claude Opus 4.8 Signed-off-by: Larry Gritz --- CHANGES.md | 6 ++++++ src/include/OpenImageIO/imageio.h | 14 ++++++++++++++ src/include/imageio_pvt.h | 1 + src/libOpenImageIO/imageinput.cpp | 17 +++++++++++++++++ src/libOpenImageIO/imageio.cpp | 9 +++++++++ testsuite/oiiotool-control/ref/out.txt | 1 + testsuite/oiiotool-control/run.py | 1 + 7 files changed, 49 insertions(+) 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 " "')