Skip to content

GHSA-38j8-88v6-jrm7 on CTRL-OS 26.05

Aliases: GHSA-38j8-88v6-jrm7

Packages: openexr

Status: Plausible

Advisory Information

OpenEXR v3.4.13 DWA UNKNOWN short inflate uninitialized pixel disclosure

Summary

A crafted DWAA/DWAB EXR chunk can declare a DWA UNKNOWN planar stream with a large uncompressed size, while the embedded zlib stream inflates to only a short prefix. OpenEXR accepts the short inflate and later copies the full declared UNKNOWN channel region to caller-visible pixel output. The unwritten tail of the DWA planar buffer can therefore expose stale heap bytes.

This is file-triggered through normal OpenEXRCore decode APIs.

Reproduction status

v3.4.13 exrcheck original/ASAN/UBSAN: PoC accepted as OK
v3.4.13 original/ASAN/UBSAN/MSAN: safe control rc=0, PoC rc=134
local main original/ASAN/UBSAN/MSAN: safe control rc=0, PoC rc=134
fresh origin/main 68203429 Release/ASAN: safe control rc=0, PoC rc=134

PoC abort message in each failing harness run:

DWA UNKNOWN short inflate leaked allocator pattern into channel 0: 4076/4096 bytes

The abort is intentional in the harness after detecting allocator-pattern bytes in decoded caller-visible pixel output.

Root cause

The DWA UNKNOWN stream decode path does not verify that the zlib output filled the declared UNKNOWN_UNCOMPRESSED_SIZE.

Relevant flow:

src/lib/OpenEXRCore/internal_dwa_compressor.h
  DwaCompressor_uncompress()
  exr_uncompress_buffer(..., compressedUnknownBuf, unknownCompressedSize,
                        me->_planarUncBuffer[UNKNOWN], unknownUncompressedSize,
                        NULL)
  passes NULL for actual_out, so a short successful inflate is not detected

src/lib/OpenEXRCore/internal_dwa_compressor.h
  case UNKNOWN
  memcpy(dcddata->_rows[row], cd->planarUncBufferEnd, dstScanlineSize)
  copies the full declared UNKNOWN channel region to output rows

The PoC is a 64x32 single HALF channel named X. That channel is classified as DWA UNKNOWN; the declared UNKNOWN uncompressed size is 4096 bytes, but the zlib stream inflates to only 20 bytes. The remaining 4076 bytes of the planar buffer are stale allocation contents and are copied to the output channel.

PoC package

poc.zip contains only required reproduction material:

poc.zip

reproduce.sh
poc_materials/base_dwaa_unknown_x.exr
poc_materials/poc_dwaa_unknown_short_20_of_4096.exr
poc_materials/fuzz_dwa_unknown_short_inflate_api.cpp
poc_materials/make_dwa_unknown_base.cpp
poc_materials/make_dwa_unknown_short.py
poc_materials/logs/final/reproduce_run.log
poc_materials/logs/final/exrcheck_*_poc.out
poc_materials/logs/final/*_poc.err

poc.zip intentionally does not include report.md, README files, checksum-only files, AFL logs, duplicate-gate logs, or source-snippet notes.

Reproduction

From the extracted poc.zip directory:

OPENEXR_ROOT=/home/br0nzu/openexr/v3.4.13 ./reproduce.sh

Expected result: safe controls exit 0; the crafted PoC exits 134 after the harness detects stale bytes in caller-visible output.

Complete validation output

The sanitizer builds do not emit an ASAN/UBSAN/MSAN stack trace here. The harness detects stale allocator-pattern bytes and intentionally aborts, so the complete stderr for each sanitizer PoC run is the single line shown below.

reproduce.sh stdout

===== exrcheck_original_poc =====
rc=0
 file /home/br0nzu/openexr/v3.4.13/report/36. OpenEXR v3.4.13 DWA UNKNOWN short inflate uninitialized pixel disclosure/poc_materials/poc_dwaa_unknown_short_20_of_4096.exr OK
===== exrcheck_asan_poc =====
rc=0
 file /home/br0nzu/openexr/v3.4.13/report/36. OpenEXR v3.4.13 DWA UNKNOWN short inflate uninitialized pixel disclosure/poc_materials/poc_dwaa_unknown_short_20_of_4096.exr OK
===== exrcheck_ubsan_poc =====
rc=0
 file /home/br0nzu/openexr/v3.4.13/report/36. OpenEXR v3.4.13 DWA UNKNOWN short inflate uninitialized pixel disclosure/poc_materials/poc_dwaa_unknown_short_20_of_4096.exr OK
===== v3413_original_base =====
rc=0
===== v3413_original_poc =====
rc=134
DWA UNKNOWN short inflate leaked allocator pattern into channel 0: 4076/4096 bytes
===== v3413_asan_base =====
rc=0
===== v3413_asan_poc =====
rc=134
DWA UNKNOWN short inflate leaked allocator pattern into channel 0: 4076/4096 bytes
===== v3413_ubsan_base =====
rc=0
===== v3413_ubsan_poc =====
rc=134
DWA UNKNOWN short inflate leaked allocator pattern into channel 0: 4076/4096 bytes
===== v3413_msan_base =====
rc=0
===== v3413_msan_poc =====
rc=134
DWA UNKNOWN short inflate leaked allocator pattern into channel 0: 4076/4096 bytes
===== main_original_base =====
rc=0
===== main_original_poc =====
rc=134
DWA UNKNOWN short inflate leaked allocator pattern into channel 0: 4076/4096 bytes
===== main_asan_base =====
rc=0
===== main_asan_poc =====
rc=134
DWA UNKNOWN short inflate leaked allocator pattern into channel 0: 4076/4096 bytes
===== main_ubsan_base =====
rc=0
===== main_ubsan_poc =====
rc=134
DWA UNKNOWN short inflate leaked allocator pattern into channel 0: 4076/4096 bytes
===== main_msan_base =====
rc=0
===== main_msan_poc =====
rc=134
DWA UNKNOWN short inflate leaked allocator pattern into channel 0: 4076/4096 bytes
===== origin_main_682_release_base =====
rc=0
===== origin_main_682_release_poc =====
rc=134
DWA UNKNOWN short inflate leaked allocator pattern into channel 0: 4076/4096 bytes
===== origin_main_682_asan_base =====
rc=0
===== origin_main_682_asan_poc =====
rc=134
DWA UNKNOWN short inflate leaked allocator pattern into channel 0: 4076/4096 bytes
PASS

v3.4.13 ASAN PoC stderr

DWA UNKNOWN short inflate leaked allocator pattern into channel 0: 4076/4096 bytes

v3.4.13 UBSAN PoC stderr

DWA UNKNOWN short inflate leaked allocator pattern into channel 0: 4076/4096 bytes

v3.4.13 MSAN PoC stderr

DWA UNKNOWN short inflate leaked allocator pattern into channel 0: 4076/4096 bytes

local main ASAN PoC stderr

DWA UNKNOWN short inflate leaked allocator pattern into channel 0: 4076/4096 bytes

local main UBSAN PoC stderr

DWA UNKNOWN short inflate leaked allocator pattern into channel 0: 4076/4096 bytes

local main MSAN PoC stderr

DWA UNKNOWN short inflate leaked allocator pattern into channel 0: 4076/4096 bytes

fresh origin/main 68203429 ASAN PoC stderr

DWA UNKNOWN short inflate leaked allocator pattern into channel 0: 4076/4096 bytes

Impact

This demonstrates information disclosure / uninitialized data exposure from decoded pixel output. An attacker-controlled EXR image can cause decoded DWA UNKNOWN channel pixels to contain stale bytes from OpenEXR's decode allocation instead of file-provided samples. Applications that display, save, transform, or transmit decoded pixels may expose those bytes.

Suggested fix

For DWA UNKNOWN data, pass an actual_out pointer to exr_uncompress_buffer() and reject the chunk unless actual_out == unknownUncompressedSize, or otherwise guarantee the entire UNKNOWN planar buffer is initialized before row copy-out.

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