From cb3ea9f74bc815c31e6460e031f80a16d27e20b6 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 20 Jun 2026 12:34:55 -0700 Subject: [PATCH] fix(cineon): more robust to invalid numbers of channels & bit depth Guard against corrupted images, which if they said they had more than the 8 channels allowed by Cineon format, would overrun array bounds while reading the file. And check for valid bit depths. Also opportunistically fix a bug I spotted where a red channel was incrementing the count of greyscale channels rather than the count of red channels -- which must have been a copy and paste error from literally 15 years ago. (But those counters are only used for channel naming, and only if there is more than one greyscale or red channel in the file. So I doubt anybody has ever hit this bug, or ever would have in the future.) Fix possible memory leak by having init() call `m_cin.SetInStream()` with a null handle to clear out the internals. Signed-off-by: Larry Gritz --- src/cineon.imageio/cineoninput.cpp | 29 ++++++++++++++++++----- testsuite/cineon/ref/out.txt | 3 +++ testsuite/cineon/run.py | 8 +++++++ testsuite/cineon/src/broken_bitdepth.cin | Bin 0 -> 2048 bytes 4 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 testsuite/cineon/src/broken_bitdepth.cin diff --git a/src/cineon.imageio/cineoninput.cpp b/src/cineon.imageio/cineoninput.cpp index d64ac4ab3d..330ba34c27 100644 --- a/src/cineon.imageio/cineoninput.cpp +++ b/src/cineon.imageio/cineoninput.cpp @@ -41,6 +41,7 @@ class CineonInput final : public ImageInput { m_stream = nullptr; } m_userBuf.clear(); + m_cin.SetInStream(nullptr); } /// Helper function - retrieve string for libcineon descriptor @@ -89,12 +90,23 @@ CineonInput::open(const std::string& name, ImageSpec& newspec) return false; } + int nchannels = m_cin.header.NumberOfElements(); + if (nchannels < 1 || nchannels > 8) { + errorfmt("Invalid number of channels {} (must be 1-8)", nchannels); + close(); + return false; + } + // create imagespec TypeDesc typedesc; int maxbits = 0; - for (int i = 0; i < m_cin.header.NumberOfElements(); i++) { - if (maxbits < m_cin.header.BitDepth(i)) - maxbits = m_cin.header.BitDepth(i); + for (int i = 0; i < nchannels; i++) { + int b(m_cin.header.BitDepth(i)); + if (b < 1 || b > 32) { + errorfmt("Invalid bitdepth in channel {}: {} bits", i, b); + return false; + } + maxbits = std::max(maxbits, b); } switch ((maxbits + 7) / 8) { case 1: typedesc = TypeDesc::UINT8; break; @@ -103,8 +115,13 @@ CineonInput::open(const std::string& name, ImageSpec& newspec) case 4: typedesc = TypeDesc::UINT32; break; default: errorfmt("Unsupported bit depth {}", maxbits); return false; } - m_spec = ImageSpec(m_cin.header.Width(), m_cin.header.Height(), - m_cin.header.NumberOfElements(), typedesc); + m_spec = ImageSpec(m_cin.header.Width(), m_cin.header.Height(), nchannels, + typedesc); + + if (!check_open(m_spec, { 0, 1 << 20, 0, 1 << 20, 0, 1, 0, 8 })) { + return false; + } + // fill channel names m_spec.channelnames.clear(); int gscount = 0, rcount = 0, gcount = 0, bcount = 0; @@ -119,7 +136,7 @@ CineonInput::open(const std::string& name, ImageSpec& newspec) break; case cineon::kPrintingDensityRed: case cineon::kRec709Red: - if (++gscount > 1) { + if (++rcount > 1) { std::string ch = Strutil::fmt::format("R{}", rcount); m_spec.channelnames.push_back(ch); } else diff --git a/testsuite/cineon/ref/out.txt b/testsuite/cineon/ref/out.txt index efe69e19aa..74ad31e404 100644 --- a/testsuite/cineon/ref/out.txt +++ b/testsuite/cineon/ref/out.txt @@ -33,3 +33,6 @@ Reading ../oiio-images/cineon/checker.cin cineon:YOffset: 0 oiio:BitsPerSample: 10 oiio:ColorSpace: "KodakLog" +oiiotool ERROR: read : "src/broken_bitdepth.cin": Invalid number of channels 255 (must be 1-8) +Full command line was: +> oiiotool --info --hash src/broken_bitdepth.cin diff --git a/testsuite/cineon/run.py b/testsuite/cineon/run.py index 9637ef88a9..e73803c963 100755 --- a/testsuite/cineon/run.py +++ b/testsuite/cineon/run.py @@ -4,7 +4,15 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +# save the error output +redirect = " >> out.txt 2>&1 " files = [ "checker.cin" ] for f in files: command += info_command ("../oiio-images/cineon/" + f) + + +# Regression tests for broken files +command += info_command ("src/broken_bitdepth.cin", verbose=False, failureok=True) + +outputs = [ "out.txt" ] diff --git a/testsuite/cineon/src/broken_bitdepth.cin b/testsuite/cineon/src/broken_bitdepth.cin new file mode 100644 index 0000000000000000000000000000000000000000..f239e66fe3aa5156b06751340b66e5ebfa851de0 GIT binary patch literal 2048 kcmZqBioeb