GHSA-prr3-4q3r-hmf3 on CTRL-OS 26.05
Aliases: GHSA-prr3-4q3r-hmf3
Packages: openexr
Status: Plausible
Advisory Information
poc-openexr-deep-zero-table.zip
Summary
A crafted compressed deep-scanline EXR with a zero-length sample-count table causes OpenEXRCore to read uninitialized heap memory in
unpack_sample_table().The malformed file reaches the issue through the normal OpenEXR C and C++ decode/check paths. The issue reproduces in OpenEXR v3.4.13 and current main.
This is a distinct current-version trigger related to CVE-2025-64181. The public deep-sample-table testcase for CVE-2025-64181 is rejected by current OpenEXR, whereas this zero-length trigger skips the sample-table decompression validation and still reaches the uninitialized read.
I request a new CVE for this distinct current-version validation bypass. I am disclosing the relationship to CVE-2025-64181 so that the OpenEXR maintainers and CNA can make the final assignment decision.
The demonstrated result is a deterministically reproducible sanitizer-detected heap use of uninitialized values. I have not demonstrated code execution, an out-of-bounds access, or proven information disclosure.
Details
Tested versions on x86-64 Linux:
- OpenEXR v3.4.13:
c1194b2cb23a1bdf76fe5e756b22e8436b9a98c9- OpenEXR current main:
55888dbd69f9e9edfc5495934fbfa4db37f474b1Both source trees were unmodified. The tests were performed with source builds of OpenEXRCore; a prebuilt PyPI wheel was not separately tested.
The PoC is 58,517 bytes and differs from the upstream
11.deep.exrimage at exactly one byte:one-based offset 2821 (0x0b04): 0x11 -> 0x00This changes the compressed deep sample-count table size from 17 to 0.
PoC SHA-256:
bb634485ba04e655cc71fee372dafcac1f6adfd5d2efdb6f0fcb2c14bde488daThe vulnerable state transition is:
exr_read_scanline_chunk_info()rejects a negative sample-table size but acceptssample_count_table_size == 0for a compressed deep scanline:https://github.com/AcademySoftwareFoundation/openexr/blob/55888dbd69f9e9edfc5495934fbfa4db37f474b1/src/lib/OpenEXRCore/chunk.c#L997-L1059
default_read_chunk()allocates and readspacked_sample_count_tableonly when the declared size is greater than zero:https://github.com/AcademySoftwareFoundation/openexr/blob/55888dbd69f9e9edfc5495934fbfa4db37f474b1/src/lib/OpenEXRCore/decoding.c#L151-L215
update_pack_unpack_ptrs()allocates the full decompressedwidth * height * sizeof(int32_t)table. The heap allocation is not initialized:https://github.com/AcademySoftwareFoundation/openexr/blob/55888dbd69f9e9edfc5495934fbfa4db37f474b1/src/lib/OpenEXRCore/decoding.c#L20-L57
exr_uncompress_chunk()decompresses the sample table only whenpacked_sample_count_tableis non-null. The zero-length state therefore skips decompression and its validation:https://github.com/AcademySoftwareFoundation/openexr/blob/55888dbd69f9e9edfc5495934fbfa4db37f474b1/src/lib/OpenEXRCore/compression.c#L513-L539
The existing zero-fill path is restricted to
EXR_COMPRESSION_NONEand does not initialize this compressed state: https://github.com/AcademySoftwareFoundation/openexr/blob/55888dbd69f9e9edfc5495934fbfa4db37f474b1/src/lib/OpenEXRCore/decoding.c#L589-L648
unpack_sample_table()then reads the uninitialized entries to check sample-count monotonicity and calculate the total sample count: https://github.com/AcademySoftwareFoundation/openexr/blob/55888dbd69f9e9edfc5495934fbfa4db37f474b1/src/lib/OpenEXRCore/decoding.c#L219-L280Representative MemorySanitizer result:
use-of-uninitialized-value #0 unpack_sample_table #1 exr_decoding_run Uninitialized value was created by a heap allocation #0 malloc #1 internal_exr_alloc #2 internal_decode_alloc_buffer #3 update_pack_unpack_ptrsValidation matrix:
Configuration Result NexusSan, two upstream OpenEXR harnesses 6/6 UMR reports MemorySanitizer C runner, current main heap-origin UMR MemorySanitizer C runner, v3.4.13 same heap-origin UMR NexusSan is a standalone object-state sanitizer. Independent MemorySanitizer builds confirm the same heap-origin initializedness violation on both tested OpenEXR revisions.
Relationship to CVE-2025-64181 / GHSA-3h9h-qfvw-98hq:
- The existing advisory lists an
unpack_sample_table (deep scanline)manifestation.- Its public
archive2testcase is 412 bytes with SHA-256:a20763b1613557f2fcc3848633a190e0afa446f49302a6c66446e9b2c18aebca.- That public testcase exits normally on all four current NexusSan harness configurations.
- The current C++ path rejects it with:
Unable to decompress sample table 4 -> 4100.- The new trigger declares a zero-length table, so no packed-table pointer is created and the decompression validation that rejects the public testcase is not reached.
- CVE-2025-64181 lists OpenEXR 3.4.3 as patched, but this distinct trigger reproduces in v3.4.13 and current main.
Suggested remediation is either to reject an invalid absent sample table before allocation or, if an all-zero deep sample table is valid, initialize the complete decompressed table to the all-zero representation regardless of compression type before
unpack_sample_table()runs.Please add an initializedness regression test for a compressed deep-scanline chunk with a zero-length sample-count table.
PoC
Attachments:
poc-openexr-deep-zero-table.zip- contains
poc.exr- contains
POC_MANIFEST.txtdeep_core_decode_runner.cmsan-main-summary.logmsan-v3.4.13-summary.lognexus-summary.logZIP SHA-256:
f00458a178b119ead5422616d37a4cdeee3cb6a28761da63dd4e42766fcd6b1dExtracted
poc.exrSHA-256:bb634485ba04e655cc71fee372dafcac1f6adfd5d2efdb6f0fcb2c14bde488daReproduction procedure:
- Check out either tested OpenEXR revision.
- Build OpenEXRCore with Clang MemorySanitizer and origin tracking. Use
-fsanitize=memory,-fsanitize-memory-track-origins=2, debug information, and frame pointers for both compilation and linking.- Compile the attached
deep_core_decode_runner.cwith the same MemorySanitizer instrumentation and link it against the instrumented OpenEXRCore build.- Extract
poc.exrand run:MSAN_OPTIONS=halt_on_error=1:exit_code=86:symbolize=1:track_origins=2 \ ./deep_core_decode_runner_msan poc.exrExpected result:
- process exit code 86;
- MemorySanitizer reports
use-of-uninitialized-valueinunpack_sample_table();- the allocation origin passes through
internal_decode_alloc_buffer()andupdate_pack_unpack_ptrs().The attached MemorySanitizer summaries were generated with this C runner. The NexusSan results were generated separately with the two upstream OpenEXR OSS-Fuzz harnesses.
Without an initializedness sanitizer, the same input may exit normally or produce nondeterministic behavior; a normal exit does not mean that the uninitialized read did not occur.
Impact
Applications that validate or decode attacker-controlled deep-scanline EXR files with an affected OpenEXRCore build can reach this flaw through normal product input.
Uninitialized heap values influence:
- sample-count monotonicity validation;
- accumulated deep sample totals;
- subsequent deep-output allocation decisions.
The directly demonstrated impact is undefined and nondeterministic behavior caused by a heap use of uninitialized values. A crash or denial of service is plausible, but a stable native crash without an initializedness sanitizer has not been demonstrated.
This vector assumes that a user or local application opens the crafted file. An unattended service that automatically ingests untrusted EXR files may justify different AV/UI metrics.
Credit requested: He Huang as finder and reporter, with acknowledgement that the issue was found using NexusSan, if acceptable.
Updates
2026-08-11 03:51 CEST
Metadata changes:
- Status for package
openexr: “Plausible”
2026-08-11 03:31 CEST
Metadata changes:
- Status for package
openexr: “New”