GHSA-54cp-3rq6-7mq8 on CTRL-OS 26.05
Aliases: GHSA-54cp-3rq6-7mq8
Packages: openexr
Status: Plausible
Advisory Information
Summary
OpenEXRUtil has a heap out-of-bounds write in
Imf_4_0::SampleCountChannel::set(int r, unsigned int newNumSamples[]).The row-based sample-count setter computes the target Y coordinate with
dataWindow.min.xinstead ofdataWindow.min.y. For a valid deep image data window wheremin.x != min.y, a valid row index can be translated into an invalid Y coordinate, causing writes before the allocated_numSamplesbuffer.This is reachable through the public OpenEXRUtil DeepImage API and can lead to heap corruption and process crashes.
Details
Affected code:
src/lib/OpenEXRUtil/ImfSampleCountChannel.cppvoid SampleCountChannel::set (int r, unsigned int newNumSamples[]) { int x = level ().dataWindow ().min.x; int y = r + level ().dataWindow ().min.x; for (int i = 0; i < pixelsPerRow (); ++i, ++x) set (x, y, newNumSamples[i]); }The
ycalculation should uselevel().dataWindow().min.y.For example, with a valid data window
(0, 1) - (1, 1)andr = 0, the expected target Y coordinate is1, but the code computes0.The lower-level
SampleCountChannel::set(int x, int y, unsigned int)then computes an index using_base + y * pixelsPerRow() + xwithout first callingboundsCheck(), despite the public header documenting the setters as bounds-checked.Observed in GDB on commit
e9133442dda6139aa395d0e87f3b00e7f31199a6:dataWindow.min = (0, 1) dataWindow.max = (1, 1) pixelsPerRow = 2 pixelsPerColumn = 1 x = 0 y = 0 _numSamples = 0x555555775590 _base = 0x555555775588 computed index = -2So the row setter writes to
_numSamples[-2], corrupting memory before the allocated sample-count array.The same vulnerable code is present in at least
v3.2.4,v3.3.5,v3.4.12, and currentmain.PoC
Minimal C++ reproducer:
#include <ImfDeepImage.h> #include <ImfSampleCountChannel.h> #include <Imath/ImathBox.h> namespace IMF = OPENEXR_IMF_NAMESPACE; namespace IM = IMATH_NAMESPACE; int main() { IMF::DeepImage img; img.insertChannel("Z", IMF::FLOAT, 1, 1, false); img.resize( IM::Box2i(IM::V2i(0, 1), IM::V2i(1, 1)), IMF::ONE_LEVEL, IMF::ROUND_DOWN); IMF::SampleCountChannel& sc = img.level(0).sampleCounts(); unsigned int counts[2] = {1, 1}; // row 0 is valid because pixelsPerColumn() == 1. // Expected y: dataWindow.min.y + 0 == 1. // Actual y: dataWindow.min.x + 0 == 0. sc.set(0, counts); return 0; }Expected result under a memory-debug build is an out-of-bounds write before
_numSamples. In non-ASAN builds this can later abort with:free(): invalid pointerRepresentative stack from fuzzing/GDB:
Imf_4_0::SampleCountChannel::set(int, int, unsigned int) src/lib/OpenEXRUtil/ImfSampleCountChannel.cpp Imf_4_0::SampleCountChannel::set(int, unsigned int*) src/lib/OpenEXRUtil/ImfSampleCountChannel.cpp:229Impact
This is a heap out-of-bounds write in OpenEXRUtil's public DeepImage API.
Applications that use
SampleCountChannel::set(row, array)with image data windows whose X and Y origins differ can corrupt heap memory and crash. The confirmed impact is denial of service through heap corruption. I have not confirmed that a crafted EXR file directly reaches this row setter through the standard file loading path.Suggested fix:
int x = level ().dataWindow ().min.x; int y = r + level ().dataWindow ().min.y;Also consider validating
rin the row setter and callingboundsCheck(x, y)inSampleCountChannel::set(int x, int y, unsigned int)to match the documented bounds-checked behavior.
Updates
2026-07-10 18:47 CEST
Metadata changes:
- Status for package
openexr: “Plausible”
2026-07-07 22:43 CEST
Metadata changes:
- Status for package
openexr: “New”
(Amended on: 2026-07-10 18:46 CEST)