fix(cineon): more robust to invalid numbers of channels & bit depth (#5250)

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 <lg@larrygritz.com>
This commit is contained in:
Larry Gritz
2026-06-27 07:45:15 -07:00
committed by GitHub
parent 52a4a98ceb
commit 908f22f552
4 changed files with 34 additions and 6 deletions
+23 -6
View File
@@ -41,6 +41,7 @@ private:
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
+3
View File
@@ -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
+8
View File
@@ -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" ]
Binary file not shown.