PT-2026-59488 · Pypi · Openexr
Published
2026-07-13
·
Updated
2026-07-13
CVSS v4.0
8.7
High
| Vector | AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N |
Summary
The PXR24 decompression function undo pxr24 impl in OpenEXR (internal pxr24.c) ignores the actual decompressed size (outSize) returned by exr uncompress buffer() and instead reads from the scratch buffer based solely on the expected size (uncompressed size) derived from the header metadata.
Additionally, exr uncompress buffer() (compression.c:202) treats LIBDEFLATE SHORT OUTPUT (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.
- compression.c:202–205 — LIBDEFLATE SHORT OUTPUT treated as success
else if (res == LIBDEFLATE SHORT OUTPUT)
{
/* TODO: is this an error? */
return EXR ERR SUCCESS;
}libdeflate zlib decompress ex() returns LIBDEFLATE SHORT OUTPUT 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 actual out. However, the function does not treat this condition as an error and instead returns success.
- internal pxr24.c:279–287 — outSize return value ignored
rstat = exr uncompress buffer(
decode->context, compressed data, comp buf size,
scratch data, scratch size, &outSize); // outSize = actual bytes written
if (rstat != EXR ERR SUCCESS) return rstat;
// outSize is never referenced afterwards.
// The loop below reads the entire scratch data buffer based on
// uncompressed size (the header-derived expected size).
for (int y = 0; y < decode->chunk.height; ++y) { ... }After exr uncompress buffer() returns success, the code does not verify whether the actual decompressed size (outSize) matches the expected size (uncompressed size). The subsequent byte-plane reconstruction loop reads from the scratch buffer up to uncompressed size bytes. As a result, the region between outSize and uncompressed size consists of uninitialized heap memory, which is then read by the decoder.
Affected component
- src/lib/OpenEXRCore/internal pxr24.c — undo pxr24 impl() (line 261–399)
- src/lib/OpenEXRCore/compression.c — exr uncompress buffer() (line 202–205)
PoC
Please refer to the atta
poc.zip
ched archive file and proceed after extracting it.
- git clone https://github.com/AcademySoftwareFoundation/openexr.git
- mv poc openexr/
- cd openexr
- docker build -f poc/Dockerfile -t pxr24-poc .
- docker run --rm pxr24-poc
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.
Fix
Found an issue in the description? Have something to add? Feel free to write us 👾
Related Identifiers
Affected Products
Openexr