# OpenEXR's CompositeDeepScanLine integer-overflow leads to heap OOB write

Integer overflow in OpenEXR CompositeDeepScanLine sample accounting wraps a 32-bit total, undersizing the composite buffer so deep decode writes past the heap allocation.

- Payout: Research
- Severity: high
- Program: OpenEXR
- Found by: quangIO
- Disclosed: 2026-03-02
- CVE: CVE-2026-27622
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-cr4v-6jm6-4963
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-cr4v-6jm6-4963
- Tags: area:oss domain:oss impact:dos impact:rce kind:research platform:ghsa sev:high technique:fuzzing technique:source-review vuln:dos vuln:memory-corruption

**Target:** CompositeDeepScanLine::readPixels in lib/OpenEXR (multipart deep scanline EXR decode)
**Root cause:** Per-pixel sample totals accumulate in vector<unsigned int> total_sizes; across many parts with huge per-pixel counts the sum wraps mod 2^32, so overall_sample_count and samples[channel].resize() allocate far less than the true sample volume that decode then writes.
**Steps:**
1. Build with ASAN/UBSAN: `cmake -S . -B build-asan -DOPENEXR_BUILD_POC=ON -DCMAKE_C_FLAGS='-fsanitize=address,undefined -fno-omit-frame-pointer' -DCMAKE_CXX_FLAGS='-fsanitize=address,undefined -fno-omit-frame-pointer'`
2. Generate malicious multipart deep EXR: `./build-asan/poc/composite_writer --profile low-ram --file /tmp/composite_decode_focus.exr` (86 malicious parts x 50,000,000 samples, RLE/ZIPS compressed, ~25 MiB file)
3. Run reader harness that opens file, selects DEEPSCANLINE parts, adds to CompositeDeepScanLine, binds FrameBuffer, calls readPixels: `./build-asan/poc/simple_exr_reader /tmp/composite_decode_focus.exr`
4. Observe ASAN heap-buffer-overflow WRITE of size 4 in `generic_unpack_deep_pointers` (unpack.c:1374) past the vector<float> allocated at ImfCompositeDeepScanLine.cpp:535 (820132016-byte region)
**How found:** Reporter traced the composite read path (MultiPartInputFile + DeepScanLineInputPart + CompositeDeepScanLine) and noticed sample totals were accumulated in unsigned int while decode pointer progression used larger counters, hypothesizing a wrap between allocation size and write volume.
**Exploitation:** Attacker crafts a multipart deep EXR with many parts each declaring very large per-pixel sample counts; compression keeps the file small (~25 MiB) while decode pressure is huge. When a victim app composites the deep parts, the wrapped uint32 total yields a ~38 MiB allocation while decode writes ~286 MiB per part, corrupting the heap.
**Insight:** When a library sums attacker-controlled counts across multiple parts/files into a 32-bit accumulator but a downstream decoder tracks the same quantity in a wider type, the mismatch between allocation and write volume is the bug — grep for `unsigned int` accumulators feeding resize() in multi-part composite paths.

## Proof of concept (copied exactly from the original report)

**ASAN build and PoC writer/reader commands**

```
cmake -S . -B build-asan \
  -DOPENEXR_BUILD_POC=ON \
  -DCMAKE_BUILD_TYPE=RelWithDebInfo \
  -DCMAKE_C_FLAGS='-fsanitize=address,undefined -fno-omit-frame-pointer' \
  -DCMAKE_CXX_FLAGS='-fsanitize=address,undefined -fno-omit-frame-pointer' \
  -DCMAKE_EXE_LINKER_FLAGS='-fsanitize=address,undefined' \
  -DCMAKE_SHARED_LINKER_FLAGS='-fsanitize=address,undefined'

cmake --build build-asan --target composite_writer simple_exr_reader -j

Generate malicious file (decode-path focused profile):

ASAN_OPTIONS=detect_leaks=0 timeout 180s \
  ./build-asan/poc/composite_writer \
  --profile low-ram \
  --file /tmp/composite_decode_focus.exr

Trigger:

ASAN_OPTIONS=detect_leaks=0 timeout 30s \
  ./build-asan/poc/simple_exr_reader /tmp/composite_decode_focus.exr
```

