Skip to content

GHSA-r24r-74rc-588f

CVE Information

exrmultipart convert ILP32 channelstore heap buffer overflow

poc.zip

Summary

A malformed public EXR can trigger an ILP32 allocation-size truncation in the shipped exrmultipart -convert tool. The convert path sizes std::vector<char> channelstore from samplesize * pixel_count; on ILP32 the product wraps, then InputPart::readPixels() reads scanline bytes into the undersized buffer.

This reproduces on OpenEXR v3.4.13 and on the tested current-main ILP32 build.

Affected path

  • src/bin/exrmultipart/exrmultipart.cpp:291: reads attacker-controlled dataWindow.
  • exrmultipart.cpp:295: computes pixel_count.
  • exrmultipart.cpp:324-326: calls channelstore[i].resize(samplesize * pixel_count) without checking that the product fits ILP32 size_t.
  • exrmultipart.cpp:332/340: installs the undersized buffer in Slice objects.
  • exrmultipart.cpp:357: InputPart::readPixels() writes full scanline data into that undersized buffer.

Reproduction

From this directory:

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

Fresh verification results:

date_utc=2026-06-25T15:11:12Z
root=/home/br0nzu/openexr/v3.4.13
dir=/home/br0nzu/openexr/v3.4.13/report/64. OpenEXR v3.4.13 exrmultipart convert ILP32 channelstore heap buffer overflow
onechan_sha256=45abbc95629b17d84bed6fc68aea3272000c5f9e04a7ed4138f37dee89077887
rgba_sha256=a602a4aad612f35201b60d02faa8ac1e7e899a55542127ff19a9f14fe620302a

== v3413_original ==
bin=poc_materials/bin/exrmultipart_ilp32_original
input=poc_materials/exrmultipart_onechan_w65537_h65537_3chunks_none.exr
env=
rc=139
stdout_bytes=177 stderr_bytes=43 output_size=524290

== v3413_asan ==
bin=poc_materials/bin/exrmultipart_ilp32_asan
input=poc_materials/exrmultipart_onechan_w65537_h65537_3chunks_none.exr
env=ASAN_OPTIONS=detect_leaks=0:halt_on_error=1:abort_on_error=1:symbolize=1
rc=134
stdout_bytes=173 stderr_bytes=6541 output_size=524290

== v3413_ubsan ==
bin=poc_materials/bin/exrmultipart_ilp32_ubsan
input=poc_materials/exrmultipart_onechan_w65537_h65537_3chunks_none.exr
env=UBSAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_stacktrace=1:symbolize=1
rc=1
stdout_bytes=174 stderr_bytes=81 output_size=524618

== current_main_original ==
bin=poc_materials/bin/exrmultipart_current_main_ilp32_original
input=poc_materials/exrmultipart_onechan_w65537_h65537_3chunks_none.exr
env=
rc=139
stdout_bytes=184 stderr_bytes=43 output_size=524290

== current_main_asan ==
bin=poc_materials/bin/exrmultipart_current_main_ilp32_asan
input=poc_materials/exrmultipart_onechan_w65537_h65537_3chunks_none.exr
env=ASAN_OPTIONS=detect_leaks=0:halt_on_error=1:abort_on_error=1:symbolize=1
rc=134
stdout_bytes=180 stderr_bytes=6564 output_size=524290

== current_main_ubsan ==
bin=poc_materials/bin/exrmultipart_current_main_ilp32_ubsan
input=poc_materials/exrmultipart_onechan_w65537_h65537_3chunks_none.exr
env=UBSAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_stacktrace=1:symbolize=1
rc=1
stdout_bytes=181 stderr_bytes=94 output_size=524618

Impact

On ILP32 builds, an attacker-controlled EXR passed to exrmultipart -convert can cause heap memory corruption. Confirmed impact is deterministic crash plus ASAN-confirmed heap out-of-bounds write. Practical exposure is mainly automated conversion or asset-ingestion pipelines that invoke exrmultipart -convert on untrusted EXR files.

Full sanitizer output

v3.4.13 ASAN

=================================================================
==730596==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xf76ca802 at pc 0x5672177a bp 0xff839648 sp 0xff839220
WRITE of size 131074 at 0xf76ca802 thread T0
    #0 0x56721779 in pread64 (/home/br0nzu/openexr/v3.4.13/report/64. OpenEXR v3.4.13 exrmultipart convert ILP32 channelstore heap buffer overflow/poc_materials/bin/exrmultipart_ilp32_asan+0xd3779) (BuildId: 6c23b7ce3de804855d09673cc0b1f228ddcb072e)
    #1 0x56984c81 in default_read_func /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_posix_file_impl.h:177:14
    #2 0x56980106 in dispatch_read /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/context.c:51:16
    #3 0x56a05d2b in read_uncompressed_direct /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/decoding.c:131:18
    #4 0x56a06a67 in exr_decoding_run /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/decoding.c:567:10
    #5 0x5696248f 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
    #6 0x5695f638 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
    #7 0x5695ef49 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
    #8 0x56952a37 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
    #9 0x56952966 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
    #10 0x568200f3 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
    #11 0x567e7477 in convert(std::vector<char const*, std::allocator<char const*>>, std::vector<char const*, std::allocator<char const*>>, char const*, bool) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmultipart/exrmultipart.cpp:357:12
    #12 0x567f123c in main /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmultipart/exrmultipart.cpp:716:13
    #13 0xf794ccb8  (/lib/i386-linux-gnu/libc.so.6+0x24cb8) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947)
    #14 0xf794cd7b in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x24d7b) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947)
    #15 0x56705806 in _start (/home/br0nzu/openexr/v3.4.13/report/64. OpenEXR v3.4.13 exrmultipart convert ILP32 channelstore heap buffer overflow/poc_materials/bin/exrmultipart_ilp32_asan+0xb7806) (BuildId: 6c23b7ce3de804855d09673cc0b1f228ddcb072e)

0xf76ca802 is located 0 bytes after 262146-byte region [0xf768a800,0xf76ca802)
allocated by thread T0 here:
    #0 0x567df5cb in operator new(unsigned int) (/home/br0nzu/openexr/v3.4.13/report/64. OpenEXR v3.4.13 exrmultipart convert ILP32 channelstore heap buffer overflow/poc_materials/bin/exrmultipart_ilp32_asan+0x1915cb) (BuildId: 6c23b7ce3de804855d09673cc0b1f228ddcb072e)
    #1 0x568014ea in std::__new_allocator<char>::allocate(unsigned int, void const*) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/new_allocator.h:151:27
    #2 0x568014ea in std::allocator_traits<std::allocator<char>>::allocate(std::allocator<char>&, unsigned int) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/alloc_traits.h:482:20
    #3 0x568014ea in std::_Vector_base<char, std::allocator<char>>::_M_allocate(unsigned int) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_vector.h:381:20
    #4 0x568014ea in std::vector<char, std::allocator<char>>::_M_default_append(unsigned int) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/vector.tcc:663:34
    #5 0x567e70b6 in std::vector<char, std::allocator<char>>::resize(unsigned int) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_vector.h:1016:4
    #6 0x567e70b6 in convert(std::vector<char const*, std::allocator<char const*>>, std::vector<char const*, std::allocator<char const*>>, char const*, bool) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmultipart/exrmultipart.cpp:326:25
    #7 0x567f123c in main /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmultipart/exrmultipart.cpp:716:13
    #8 0xf794ccb8  (/lib/i386-linux-gnu/libc.so.6+0x24cb8) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947)

SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/br0nzu/openexr/v3.4.13/report/64. OpenEXR v3.4.13 exrmultipart convert ILP32 channelstore heap buffer overflow/poc_materials/bin/exrmultipart_ilp32_asan+0xd3779) (BuildId: 6c23b7ce3de804855d09673cc0b1f228ddcb072e) in pread64
Shadow bytes around the buggy address:
  0xf76ca580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0xf76ca600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0xf76ca680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0xf76ca700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0xf76ca780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0xf76ca800:[02]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0xf76ca880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0xf76ca900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0xf76ca980: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0xf76caa00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0xf76caa80: 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
==730596==ABORTING
timeout: the monitored command dumped core

current main ASAN

=================================================================
==730675==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xf6fc6802 at pc 0x5664dcfa bp 0xfffeb768 sp 0xfffeb340
WRITE of size 131074 at 0xf6fc6802 thread T0
    #0 0x5664dcf9 in pread64 (/home/br0nzu/openexr/v3.4.13/report/64. OpenEXR v3.4.13 exrmultipart convert ILP32 channelstore heap buffer overflow/poc_materials/bin/exrmultipart_current_main_ilp32_asan+0x3bcf9) (BuildId: c43007184a3f7eede2aa8da1efb540b0d2e1b0ad)
    #1 0xf7b25301 in default_read_func /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/lib/OpenEXRCore/internal_posix_file_impl.h:177:14
    #2 0xf7b206e6 in dispatch_read /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/lib/OpenEXRCore/context.c:51:16
    #3 0xf7b75cdb in read_uncompressed_direct /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/lib/OpenEXRCore/decoding.c:131:18
    #4 0xf7b76a17 in exr_decoding_run /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/lib/OpenEXRCore/decoding.c:567:10
    #5 0xf7e6ddaf 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/src/lib/OpenEXR/ImfScanLineInputFile.cpp:586:23
    #6 0xf7e6aff8 in Imf_4_0::ScanLineInputFile::Data::readPixels(Imf_4_0::FrameBuffer const&, int, int) /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/lib/OpenEXR/ImfScanLineInputFile.cpp:500:21
    #7 0xf7e6a8f9 in Imf_4_0::ScanLineInputFile::readPixels(int, int) /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/lib/OpenEXR/ImfScanLineInputFile.cpp:307:12
    #8 0xf7dd7637 in Imf_4_0::InputFile::Data::readPixels(int, int) /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/lib/OpenEXR/ImfInputFile.cpp:458:20
    #9 0xf7dd7566 in Imf_4_0::InputFile::readPixels(int, int) /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/lib/OpenEXR/ImfInputFile.cpp:222:12
    #10 0xf7de0a23 in Imf_4_0::InputPart::readPixels(int, int) /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/lib/OpenEXR/ImfInputPart.cpp:78:11
    #11 0x567139f7 in convert(std::vector<char const*, std::allocator<char const*>>, std::vector<char const*, std::allocator<char const*>>, char const*, bool) /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/bin/exrmultipart/exrmultipart.cpp:357:12
    #12 0x5671d7bc in main /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/bin/exrmultipart/exrmultipart.cpp:716:13
    #13 0xf724ecb8  (/lib/i386-linux-gnu/libc.so.6+0x24cb8) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947)
    #14 0xf724ed7b in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x24d7b) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947)
    #15 0x56631d86 in _start (/home/br0nzu/openexr/v3.4.13/report/64. OpenEXR v3.4.13 exrmultipart convert ILP32 channelstore heap buffer overflow/poc_materials/bin/exrmultipart_current_main_ilp32_asan+0x1fd86) (BuildId: c43007184a3f7eede2aa8da1efb540b0d2e1b0ad)

0xf6fc6802 is located 0 bytes after 262146-byte region [0xf6f86800,0xf6fc6802)
allocated by thread T0 here:
    #0 0x5670bb4b in operator new(unsigned int) (/home/br0nzu/openexr/v3.4.13/report/64. OpenEXR v3.4.13 exrmultipart convert ILP32 channelstore heap buffer overflow/poc_materials/bin/exrmultipart_current_main_ilp32_asan+0xf9b4b) (BuildId: c43007184a3f7eede2aa8da1efb540b0d2e1b0ad)
    #1 0x5672da6a in std::__new_allocator<char>::allocate(unsigned int, void const*) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/new_allocator.h:151:27
    #2 0x5672da6a in std::allocator_traits<std::allocator<char>>::allocate(std::allocator<char>&, unsigned int) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/alloc_traits.h:482:20
    #3 0x5672da6a in std::_Vector_base<char, std::allocator<char>>::_M_allocate(unsigned int) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_vector.h:381:20
    #4 0x5672da6a in std::vector<char, std::allocator<char>>::_M_default_append(unsigned int) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/vector.tcc:663:34
    #5 0x56713636 in std::vector<char, std::allocator<char>>::resize(unsigned int) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_vector.h:1016:4
    #6 0x56713636 in convert(std::vector<char const*, std::allocator<char const*>>, std::vector<char const*, std::allocator<char const*>>, char const*, bool) /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/bin/exrmultipart/exrmultipart.cpp:326:25
    #7 0x5671d7bc in main /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/bin/exrmultipart/exrmultipart.cpp:716:13
    #8 0xf724ecb8  (/lib/i386-linux-gnu/libc.so.6+0x24cb8) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947)

SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/br0nzu/openexr/v3.4.13/report/64. OpenEXR v3.4.13 exrmultipart convert ILP32 channelstore heap buffer overflow/poc_materials/bin/exrmultipart_current_main_ilp32_asan+0x3bcf9) (BuildId: c43007184a3f7eede2aa8da1efb540b0d2e1b0ad) in pread64
Shadow bytes around the buggy address:
  0xf6fc6580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0xf6fc6600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0xf6fc6680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0xf6fc6700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0xf6fc6780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0xf6fc6800:[02]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0xf6fc6880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0xf6fc6900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0xf6fc6980: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0xf6fc6a00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0xf6fc6a80: 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
==730675==ABORTING
timeout: the monitored command dumped core