Skip to content

GHSA-v6v5-344m-64vm

CVE Information

Summary

OpenEXR v3.4.13 has an ILP32-only input-side heap out-of-bounds read in the public OpenEXRCore RLE scanline decode path. A crafted 371-byte RLE-compressed EXR declares width 536870913, height 1, and two FLOAT channels, making the logical chunk unpacked_size 4294967304 bytes (0x100000008). On 32-bit/ILP32 builds, that 64-bit size is passed to an allocation helper as size_t and truncates to 8 bytes. RLE decompression then succeeds into the tiny buffer, but the default unpacker iterates over the huge declared channel width and reads past the heap allocation.

The demonstrated impact is denial of service / memory-safety violation via out-of-bounds read. The unsanitized ILP32 build crashes with SIGSEGV, ASAN reports a heap-buffer-overflow READ in OpenEXRCore/unpack.c:1074, and UBSAN reproduces a READ SEGV in the same function. Out-of-bounds write, sensitive-data disclosure, and arbitrary code execution were not proven. LP64 builds are not demonstrated because this specific allocation does not truncate to 8 bytes there.

Affected code path

The issue is reachable through the public OpenEXRCore C decode flow declared in src/lib/OpenEXRCore/openexr_decode.h and chunk APIs in openexr_chunkio.h:

  1. exr_read_scanline_chunk_info() reports the crafted RLE chunk as width=536870913, height=1, packed_size=9, and unpacked_size=4294967304.
  2. src/lib/OpenEXRCore/decoding.c:572 calls update_pack_unpack_ptrs() before decompression.
  3. src/lib/OpenEXRCore/decoding.c:75-80 allocates decode->chunk.unpacked_size through internal_decode_alloc_buffer().
  4. src/lib/OpenEXRCore/coding.c:229-256 takes that requested size as size_t newsz; on ILP32, 4294967304 truncates to 8, so the heap buffer is only 8 bytes.
  5. exr_uncompress_chunk() decompresses the 9-byte RLE payload into the 8-byte buffer.
  6. src/lib/OpenEXRCore/decoding.c:661-664 calls the default unpack/convert routine because decode->chunk.unpacked_size > 0.
  7. src/lib/OpenEXRCore/unpack.c:1071-1079 loops over the declared channel width and reads 32-bit values from srcbuffer; after the first two floats it reads past the 8-byte allocation.

PoC arithmetic:

width = 536870913
height = 1
channels = 2 x FLOAT (4 bytes)
logical unpacked bytes = 2 * 536870913 * 4 = 4294967304 = 0x100000008
ILP32 size_t allocation = 0x00000008
RLE payload expands to 8 bytes, but unpack_32bit iterates over declared width

Package contents

poc.zip contains only the files needed to rebuild, run, and verify the standalone malformed EXR PoC.

poc.zip

reproduce.sh
repro/build_repro.sh
repro/run_repro.sh
poc_artifacts/poc.exr
poc_artifacts/probe_ilp32_core_decode_rle_file.cpp
logs/original.stdout
logs/original.stderr
logs/original.rc
logs/asan.stdout
logs/asan.stderr
logs/asan.rc
logs/ubsan.stdout
logs/ubsan.stderr
logs/ubsan.rc

Reproduction

Run from the OpenEXR v3.4.13 repository root, or set OPENEXR_ROOT to that root. The scripts expect the existing ILP32 build directories builds/ilp32_original, builds/ilp32_asan, and builds/ilp32_ubsan.

unzip -o 'report/14. OpenEXR v3.4.13 ILP32 OpenEXRCore RLE decode heap OOB read DoS/poc.zip' -d artifacts/tmp/report14-poc
cd artifacts/tmp/report14-poc
OPENEXR_ROOT=/home/br0nzu/openexr/v3.4.13 ./reproduce.sh

Observed fresh return codes:

original: 139
ASAN:     134
UBSAN:    134

Full original output

stdout


stderr

info width=536870913 height=1 packed=9 unpacked=4294967304 sizeof(size_t)=4
channel 0 A width=536870913 height=1
channel 1 B width=536870913 height=1
about exr_decoding_run
timeout: the monitored command dumped core

Full ASAN output

stdout


stderr

info width=536870913 height=1 packed=9 unpacked=4294967304 sizeof(size_t)=4
channel 0 A width=536870913 height=1
channel 1 B width=536870913 height=1
about exr_decoding_run
=================================================================
==4063486==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xf5c006f8 at pc 0x5679166c bp 0xff8ad818 sp 0xff8ad810
READ of size 4 at 0xf5c006f8 thread T0
    #0 0x5679166b in unpack_32bit /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/unpack.c:1074:44
    #1 0x5677e1c0 in exr_decoding_run /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/decoding.c:664:18
    #2 0x566faead in main /home/br0nzu/openexr/v3.4.13/report/14. OpenEXR v3.4.13 ILP32 OpenEXRCore RLE decode heap OOB read DoS/poc_artifacts/probe_ilp32_core_decode_rle_file.cpp:39:10
    #3 0xf7944cb8  (/lib/i386-linux-gnu/libc.so.6+0x24cb8) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947)
    #4 0xf7944d7b in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x24d7b) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947)
    #5 0x5661e2c6 in _start (/home/br0nzu/openexr/v3.4.13/report/14. OpenEXR v3.4.13 ILP32 OpenEXRCore RLE decode heap OOB read DoS/repro/bin/probe_ilp32_core_decode_rle_file_asan+0x252c6) (BuildId: f214fc2f4c0b3386b4c85a7576f25c56c80f4612)

0xf5c006f8 is located 0 bytes after 8-byte region [0xf5c006f0,0xf5c006f8)
allocated by thread T0 here:
    #0 0x566b5af7 in malloc (/home/br0nzu/openexr/v3.4.13/report/14. OpenEXR v3.4.13 ILP32 OpenEXRCore RLE decode heap OOB read DoS/repro/bin/probe_ilp32_core_decode_rle_file_asan+0xbcaf7) (BuildId: f214fc2f4c0b3386b4c85a7576f25c56c80f4612)
    #1 0x567dd79c in internal_exr_alloc /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/memory.c:39:12
    #2 0x567e3b3e in internal_decode_alloc_buffer /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/coding.c:256:22
    #3 0x5677e952 in update_pack_unpack_ptrs /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/decoding.c:75:14
    #4 0x5677dfee in exr_decoding_run /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/decoding.c:572:37
    #5 0x566faead in main /home/br0nzu/openexr/v3.4.13/report/14. OpenEXR v3.4.13 ILP32 OpenEXRCore RLE decode heap OOB read DoS/poc_artifacts/probe_ilp32_core_decode_rle_file.cpp:39:10
    #6 0xf7944cb8  (/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:1074:44 in unpack_32bit
Shadow bytes around the buggy address:
  0xf5c00400: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0xf5c00480: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0xf5c00500: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0xf5c00580: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0xf5c00600: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0xf5c00680: fa fa fa fa fa fa fa fa fa fa 00 fa fa fa 00[fa]
  0xf5c00700: fa fa 00 01 fa fa 00 fa fa fa 02 fa fa fa 02 fa
  0xf5c00780: fa fa fd fd fa fa 00 00 fa fa fa fa fa fa fa fa
  0xf5c00800: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0xf5c00880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0xf5c00900: 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
==4063486==ABORTING
timeout: the monitored command dumped core

Full UBSAN output

stdout


stderr

info width=536870913 height=1 packed=9 unpacked=4294967304 sizeof(size_t)=4
channel 0 A width=536870913 height=1
channel 1 B width=536870913 height=1
about exr_decoding_run
UndefinedBehaviorSanitizer:DEADLYSIGNAL
==4063510==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address 0x589b8000 (pc 0x5670453c bp 0xff807d98 sp 0xff807d20 T4063510)
==4063510==The signal is caused by a READ memory access.
    #0 0x5670453c in unpack_32bit /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/unpack.c:1074:44
    #1 0x566f80d6 in exr_decoding_run /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/decoding.c:664:18
    #2 0x566ab0a3 in main /home/br0nzu/openexr/v3.4.13/report/14. OpenEXR v3.4.13 ILP32 OpenEXRCore RLE decode heap OOB read DoS/poc_artifacts/probe_ilp32_core_decode_rle_file.cpp:39:10
    #3 0xf78f3cb8  (/lib/i386-linux-gnu/libc.so.6+0x24cb8) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947)
    #4 0xf78f3d7b in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x24d7b) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947)
    #5 0x5667ae66 in _start (/home/br0nzu/openexr/v3.4.13/report/14. OpenEXR v3.4.13 ILP32 OpenEXRCore RLE decode heap OOB read DoS/repro/bin/probe_ilp32_core_decode_rle_file_ubsan+0x3ce66) (BuildId: 9a58565458d8ca3477e294422d45478de208ae97)

UndefinedBehaviorSanitizer can not provide additional info.
SUMMARY: UndefinedBehaviorSanitizer: SEGV /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/unpack.c:1074:44 in unpack_32bit
==4063510==ABORTING
timeout: the monitored command dumped core

Impact

Applications that build OpenEXR v3.4.13 for 32-bit/ILP32 targets and decode attacker-controlled RLE-compressed EXR files through OpenEXRCore can be crashed by a small malformed file. The demonstrated impact is denial of service via SIGSEGV in an unsanitized original binary, plus sanitizer-confirmed heap out-of-bounds read in unpack_32bit().