PYSEC-2026-1746
Vulnerability from pysec - Published: 2026-07-07 16:02 - Updated: 2026-07-07 16:02Summary
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 LossyDctDecoder_execute function (from src/lib/OpenEXRCore/internal_dwa_decoder.h, when SSE2 is enabled), the following code is used to copy data from the chunks:
// 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)
{
_mm_storeu_si128 (dst, _mm_loadu_si128 (src)); //
src += 8 * 8; // <--- si128 pointer incremented as a uint16_t
dst += 8;
}
}
The issue arises because the src pointer, which is a si128 pointer, is incremented by 8*8, as if it were a uint16_t pointer (64 * uint16_t == 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 dwadecoder_crash.exr file from the following link:
https://github.com/ShielderSec/poc/tree/main/CVE-2025-48072
- Compile the
exrcheckbinary in a macOS or GNU/Linux machine with ASAN. - Open the
dwadecoder_crash.exrfile with the following command:
exrcheck dwadecoder_crash.exr
- Notice that
exrcheckcrashes 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 LossyDctDecoder_execute /root/openexr/src/lib/OpenEXRCore/internal_dwa_decoder.h:650:48
#1 0x55e590dae18d in DwaCompressor_uncompress /root/openexr/src/lib/OpenEXRCore/internal_dwa_compressor.h:1132:30
#2 0x55e590da9960 in internal_exr_undo_dwaa /root/openexr/src/lib/OpenEXRCore/internal_dwa.c:202:18
#3 0x55e590d42d03 in exr_uncompress_chunk /root/openexr/src/lib/OpenEXRCore/compression.c:516:14
#4 0x55e590dc3132 in exr_decoding_run /root/openexr/src/lib/OpenEXRCore/decoding.c:580:14
#5 0x55e590c7d78f in Imf_3_4::(anonymous namespace)::ScanLineProcess::run_decode(_priv_exr_context_t const*, int, Imf_3_4::FrameBuffer const*, int, int, std::vector<Imf_3_4::Slice, std::allocator<Imf_3_4::Slice>> const&) /root/openexr/src/lib/OpenEXR/ImfScanLineInputFile.cpp:585:23
#6 0x55e590c83ed7 in Imf_3_4::ScanLineInputFile::Data::readPixels(Imf_3_4::FrameBuffer const&, int, int) /root/openexr/src/lib/OpenEXR/ImfScanLineInputFile.cpp:499:21
#7 0x55e590c73c97 in Imf_3_4::ScanLineInputFile::readPixels(int, int) /root/openexr/src/lib/OpenEXR/ImfScanLineInputFile.cpp:306:12
#8 0x55e590c73c97 in Imf_3_4::InputFile::Data::readPixels(int, int) /root/openexr/src/lib/OpenEXR/ImfInputFile.cpp:446:20
#9 0x55e590c1f92f in Imf_3_4::InputFile::readPixels(int) /root/openexr/src/lib/OpenEXR/ImfInputFile.cpp:228:12
#10 0x55e590c1f92f in Imf_3_4::InputPart::readPixels(int) /root/openexr/src/lib/OpenEXR/ImfInputPart.cpp:70:11
#11 0x55e590c1f92f in bool Imf_3_4::(anonymous namespace)::readScanline<Imf_3_4::InputPart>(Imf_3_4::InputPart&, bool, bool) /root/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:239:20
#12 0x55e590c1f92f in Imf_3_4::(anonymous namespace)::readMultiPart(Imf_3_4::MultiPartInputFile&, bool, bool) /root/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:879:28
#13 0x55e590c155af in bool Imf_3_4::(anonymous namespace)::runChecks<char const*>(char const*&, bool, bool) /root/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:1132:21
#14 0x55e590c155af in Imf_3_4::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 __libc_start_call_main csu/../sysdeps/npthttps://gitlab.com/qemu-project/qemu/-/issuesl/libc_start_call_main.h:58:16
#18 0x7f4259e2a28a in __libc_start_main csu/../csu/libc-start.c:360:3
#19 0x55e590ac67d4 in _start (/root/openexr/_build_afl_asan/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/_build_afl_asan/bin/exrcheck+0x273623) (BuildId: 49c2658b2f9ddef9)
#1 0x55e590db11b1 in LossyDctDecoder_execute /root/openexr/src/lib/OpenEXRCore/internal_dwa_decoder.h:324:22
#2 0x55e590dae18d in DwaCompressor_uncompress /root/openexr/src/lib/OpenEXRCore/internal_dwa_compressor.h:1132:30
#3 0x55e590da9960 in internal_exr_undo_dwaa /root/openexr/src/lib/OpenEXRCore/internal_dwa.c:202:18
#4 0x55e590d42d03 in exr_uncompress_chunk /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.
| Name | purl | openexr | pkg:pypi/openexr |
|---|
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "openexr",
"purl": "pkg:pypi/openexr"
},
"ranges": [
{
"events": [
{
"introduced": "3.3.2"
},
{
"fixed": "3.3.3"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"3.3.2"
]
}
],
"aliases": [
"CVE-2025-48072",
"GHSA-4r7w-q3jg-ff43"
],
"details": "### Summary\nThe 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.\n\n### Details\n\nIn the `LossyDctDecoder_execute` function (from `src/lib/OpenEXRCore/internal_dwa_decoder.h`, when SSE2 is enabled), the following code is used to copy data from the chunks:\n\n```cpp\n// no-op conversion to linear\nfor (int y = 8 * blocky; y \u003c 8 * blocky + maxY; ++y)\n{\n __m128i* restrict dst = (__m128i *) chanData[comp]-\u003e_rows[y];\n __m128i const * restrict src = (__m128i const *)\u0026rowBlock[comp][(y \u0026 0x7) * 8];\n\n for (int blockx = 0; blockx \u003c numFullBlocksX; ++blockx)\n {\n _mm_storeu_si128 (dst, _mm_loadu_si128 (src)); //\n\n src += 8 * 8; // \u003c--- si128 pointer incremented as a uint16_t\n dst += 8;\n }\n}\n```\n\nThe issue arises because the `src` pointer, which is a `si128` pointer, is incremented by `8*8`, as if it were a `uint16_t`\u00a0pointer (64 * uint16_t == 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.\n\n### PoC\n\nIn 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.\n\nNOTE: please download the `dwadecoder_crash.exr` file from the following link: \n\nhttps://github.com/ShielderSec/poc/tree/main/CVE-2025-48072\n\n1. Compile the `exrcheck` binary in a macOS or GNU/Linux machine with ASAN.\n2. Open the `dwadecoder_crash.exr` file with the following command:\n\n```\nexrcheck dwadecoder_crash.exr\n```\n\n3. Notice that `exrcheck` crashes with ASAN stack-trace.\n\n```\n==2297956==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x52500000a110 at pc 0x55e590db7bf1 bp 0x7fff948bb110 sp 0x7fff948bb108\nREAD of size 16 at 0x52500000a110 thread T0\n #0 0x55e590db7bf0 in LossyDctDecoder_execute /root/openexr/src/lib/OpenEXRCore/internal_dwa_decoder.h:650:48\n #1 0x55e590dae18d in DwaCompressor_uncompress /root/openexr/src/lib/OpenEXRCore/internal_dwa_compressor.h:1132:30\n #2 0x55e590da9960 in internal_exr_undo_dwaa /root/openexr/src/lib/OpenEXRCore/internal_dwa.c:202:18\n #3 0x55e590d42d03 in exr_uncompress_chunk /root/openexr/src/lib/OpenEXRCore/compression.c:516:14\n #4 0x55e590dc3132 in exr_decoding_run /root/openexr/src/lib/OpenEXRCore/decoding.c:580:14\n #5 0x55e590c7d78f in Imf_3_4::(anonymous namespace)::ScanLineProcess::run_decode(_priv_exr_context_t const*, int, Imf_3_4::FrameBuffer const*, int, int, std::vector\u003cImf_3_4::Slice, std::allocator\u003cImf_3_4::Slice\u003e\u003e const\u0026) /root/openexr/src/lib/OpenEXR/ImfScanLineInputFile.cpp:585:23\n #6 0x55e590c83ed7 in Imf_3_4::ScanLineInputFile::Data::readPixels(Imf_3_4::FrameBuffer const\u0026, int, int) /root/openexr/src/lib/OpenEXR/ImfScanLineInputFile.cpp:499:21\n #7 0x55e590c73c97 in Imf_3_4::ScanLineInputFile::readPixels(int, int) /root/openexr/src/lib/OpenEXR/ImfScanLineInputFile.cpp:306:12\n #8 0x55e590c73c97 in Imf_3_4::InputFile::Data::readPixels(int, int) /root/openexr/src/lib/OpenEXR/ImfInputFile.cpp:446:20\n #9 0x55e590c1f92f in Imf_3_4::InputFile::readPixels(int) /root/openexr/src/lib/OpenEXR/ImfInputFile.cpp:228:12\n #10 0x55e590c1f92f in Imf_3_4::InputPart::readPixels(int) /root/openexr/src/lib/OpenEXR/ImfInputPart.cpp:70:11\n #11 0x55e590c1f92f in bool Imf_3_4::(anonymous namespace)::readScanline\u003cImf_3_4::InputPart\u003e(Imf_3_4::InputPart\u0026, bool, bool) /root/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:239:20\n #12 0x55e590c1f92f in Imf_3_4::(anonymous namespace)::readMultiPart(Imf_3_4::MultiPartInputFile\u0026, bool, bool) /root/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:879:28\n #13 0x55e590c155af in bool Imf_3_4::(anonymous namespace)::runChecks\u003cchar const*\u003e(char const*\u0026, bool, bool) /root/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:1132:21\n #14 0x55e590c155af in Imf_3_4::checkOpenEXRFile(char const*, bool, bool, bool) /root/openexr/src/lib/OpenEXRUtil/ImfCheckFile.cpp:1796:19\n #15 0x55e590ba5abe in exrCheck(char const*, bool, bool, bool, bool) /root/openexr/src/bin/exrcheck/main.cpp:96:16\n #16 0x55e590ba6fbe in main /root/openexr/src/bin/exrcheck/main.cpp:164:29\n #17 0x7f4259e2a1c9 in __libc_start_call_main csu/../sysdeps/npthttps://gitlab.com/qemu-project/qemu/-/issuesl/libc_start_call_main.h:58:16\n #18 0x7f4259e2a28a in __libc_start_main csu/../csu/libc-start.c:360:3\n #19 0x55e590ac67d4 in _start (/root/openexr/_build_afl_asan/bin/exrcheck+0x1d87d4) (BuildId: 49c2658b2f9ddef9)\n\n0x52500000a110 is located 752 bytes after 9504-byte region [0x525000007900,0x525000009e20)\nallocated by thread T0 here:\n #0 0x55e590b61623 in malloc (/root/openexr/_build_afl_asan/bin/exrcheck+0x273623) (BuildId: 49c2658b2f9ddef9)\n #1 0x55e590db11b1 in LossyDctDecoder_execute /root/openexr/src/lib/OpenEXRCore/internal_dwa_decoder.h:324:22\n #2 0x55e590dae18d in DwaCompressor_uncompress /root/openexr/src/lib/OpenEXRCore/internal_dwa_compressor.h:1132:30\n #3 0x55e590da9960 in internal_exr_undo_dwaa /root/openexr/src/lib/OpenEXRCore/internal_dwa.c:202:18\n #4 0x55e590d42d03 in exr_uncompress_chunk /root/openexr/src/lib/OpenEXRCore/compression.c:516:14\n```\n\n### Impact\nAn 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.",
"id": "PYSEC-2026-1746",
"modified": "2026-07-07T16:02:59.723169Z",
"published": "2026-07-07T16:02:59.723169Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/AcademySoftwareFoundation/openexr/security/advisories/GHSA-4r7w-q3jg-ff43"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-48072"
},
{
"type": "WEB",
"url": "https://github.com/AcademySoftwareFoundation/openexr/commit/2d09449427b13a05f7c31a98ab2c4347c23db361"
},
{
"type": "PACKAGE",
"url": "https://github.com/AcademySoftwareFoundation/openexr"
},
{
"type": "WEB",
"url": "https://github.com/AcademySoftwareFoundation/openexr/releases/tag/v3.3.3"
},
{
"type": "WEB",
"url": "https://github.com/ShielderSec/poc/tree/main/CVE-2025-48072"
},
{
"type": "PACKAGE",
"url": "https://pypi.org/project/openexr"
},
{
"type": "ADVISORY",
"url": "https://github.com/advisories/GHSA-4r7w-q3jg-ff43"
}
],
"severity": [
{
"score": "CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:N/VA:H/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "OpenEXR Out of Bounds Heap Read due to Bad Pointer Arithmetic in LossyDctDecoder_execute"
}
Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline
Sightings and recorded dates, grouped by day · UTC
How durations are calculated
Following the VTEM framework (Black Hat USA 2015), time to patch starts when the vendor or a coordinator was informed, total time to patch at discovery, and disclosure to patch at public disclosure; vendor response time runs from the report to its acknowledgement. Time to exploit and time unpatched start at the earliest discovery or report, falling back to public disclosure. Unpatched time ends at the first recorded patch, or the calculation time when no patch is recorded. Exploitation to patch shows the interval between those two events; a published proof of concept is a public exploit, not exploitation.
Each result shows its source dates. A negative interval means the end event came first; when date-only inputs leave the order open, the result says so. Date-only inputs are approximate. The earliest evidence wins: KEV report dates and sightings are labelled as such. Records describing another vulnerability are listed but never calculated from, and a PoC alone does not establish exploitation. Missing patch evidence does not prove that no patch exists. Filters below change the event list only.
Loading timeline…
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.