GHSA-hphq-wq62-4mj3
CVE Information
Summary
OpenEXR’s planar HTJ2K decoder uses mixed signed and unsigned arithmetic when calculating the final row of a decoded chunk. A small EXR file with a negative data-window Y origin and vertical channel subsampling causes the loop endpoint to wrap to a very large positive value.
As a result, OpenEXR continues requesting component rows after the declared image data has been exhausted. In a release build, decoding remains CPU-bound until externally terminated.
An attacker who can supply an EXR file to an application using OpenEXR can occupy a decoder thread or worker with a 448-byte file, causing a denial of service.
The issue is dynamically confirmed in OpenEXR 3.4.13. The vulnerable expression is also present in OpenEXR 3.4.0 and was present on the default branch when inspected on July 16, 2026.
Suggested severity: Moderate / Medium
Suggested weaknesses:
- CWE-190: Integer Overflow or Wraparound
- CWE-835: Loop with Unreachable Exit Condition
No memory corruption, code execution, information disclosure, or persistence impact is claimed.
Details
The issue is in the planar HTJ2K decoding path in:
src/lib/OpenEXRCore/internal_ht.cpp
image_heightis stored as an unsigned OpenJPH integer:ojph::ui32 image_height = siz.get_image_extent().y - siz.get_image_offset().y;The planar decoding loop then adds this unsigned value to the signed chunk origin:
for (int64_t y = decode->chunk.start_y; y < image_height + decode->chunk.start_y; y++)For the supplied proof-of-concept file:
image_height = 32 decode->chunk.start_y = -64The intended endpoint is:
-64 + 32 = -32However, because
image_heightis unsigned, the usual arithmetic conversions cause the negativestart_yvalue to be converted to an unsigned integer. The endpoint therefore becomes:4294967264The loop begins at
-64but attempts to continue until it reaches this wrapped positive endpoint.The proof-of-concept file uses a channel with
ySampling=2, which selects the planar decoder path. OpenEXR then continues callingcs.pull()beyond the component’s declared reconstruction height.In a release build, the supplied direct C++ reproduction consumed approximately one CPU core until terminated after five seconds. In a debug build, an OpenJPH component-bound assertion was observed after the declared rows were exhausted.
The affected logic can be corrected by calculating the endpoint entirely in a signed 64-bit type:
const int64_t start_y = static_cast<int64_t>(decode->chunk.start_y); const int64_t end_y = start_y + static_cast<int64_t>(image_height); for (int64_t y = start_y; y < end_y; ++y) { // Existing decoding logic }A regression test should decode an HTJ2K file with all of the following properties:
- A negative Y data-window origin
- Vertical channel subsampling
- Planar HTJ2K decoding
- A valid, bounded component height
The test should verify that decoding completes and returns the expected number of pixels.
PoC
The attached submission package contains:
poc/neg64-y2.exr poc/zero-y2.exr poc/neg64-y1.exr poc/poc.py poc/reproduce-cpp.sh poc/reproduce-python.shThe trigger file has the following properties:
Filename: poc/neg64-y2.exr Size: 448 bytes SHA-256: 9140ba9fc3c1d708f006eb060074aa3d4b40463dd9a337d799d4c237f5a940fe Storage: Scanline Compression: HTJ2K32_COMPRESSION Data window: (0,-64)-(255,-33) Display window: (0,-64)-(255,-33) Channel: One HALF Y channel Channel sampling: 1x2Direct C++ reproduction
Build OpenEXR 3.4.13 and make its
pkg-configfiles available throughPKG_CONFIG_PATH.From the extracted submission package, run:
cd poc chmod +x reproduce-cpp.sh ./reproduce-cpp.shExpected results:
zero-y2.exr: Decode completes successfully. neg64-y1.exr: Decode completes successfully. neg64-y2.exr: The program prints READ_BEGIN but does not print READ_OK before the five-second timeout.The supplied direct C++ execution log records approximately:
Elapsed time: 5.00 seconds User CPU time: 4.98 seconds CPU utilization: 99% Exit status: 124Exit status
124is produced by GNUtimeoutafter terminating the non-returning decoder process.Python binding reproduction
On Linux with Python 3, virtual-environment support, and GNU
timeout, run:cd poc chmod +x reproduce-python.sh ./reproduce-python.shThe script:
- Creates a local Python virtual environment.
- Installs exactly
OpenEXR==3.4.13.- Verifies the SHA-256 of the trigger file.
- Decodes the two control files.
- Attempts to decode the candidate file with a five-second timeout.
Expected result:
zero-y2.exr: Decode completes. neg64-y1.exr: Decode completes. neg64-y2.exr: Decode does not return and is terminated after five seconds.The Python reproduction confirms the non-returning behavior through OpenEXR’s public bindings. The direct C++ reproduction provides the stronger evidence that the process is actively consuming CPU rather than waiting on an external resource.
Impact
This is a denial-of-service vulnerability in OpenEXR’s HTJ2K decoder.
Any application that decodes attacker-controlled or otherwise untrusted EXR files may be affected, including:
- Image viewers and editors
- Media-processing pipelines
- Thumbnail or preview generators
- Asset-ingestion services
- Render-management systems
- Conversion services
- Automated content-analysis systems
A 448-byte EXR file is sufficient to occupy a decoding thread or worker until the operation is externally terminated.
Applications that decode files synchronously may become unresponsive. Services with limited worker pools may experience worker exhaustion if multiple malicious files are processed concurrently. Batch-processing systems may stall indefinitely on the affected file unless they enforce per-file execution limits.
The practical severity depends on the surrounding application:
- Applications without decode timeouts or worker isolation are most exposed.
- Applications that process files automatically may require no additional user interaction after upload or ingestion.
- Applications using isolated workers with strict execution deadlines may limit the effect to termination and restart of an individual worker.
OpenEXR itself does not establish whether the file arrives through a local or network interface, so network reachability depends on the integrating application.
This report claims availability impact only. No evidence of memory corruption, arbitrary code execution, information disclosure, or persistent modification was observed. openexr-htj2k-negative-origin-dos.zip