GHSA-7828-c4f6-7v6p
CVE Information
Summary
A valid scanline EXR with a subsampled channel can make OpenEXR's scanline reader write decoded ZIP chunk rows into the wrong compact
FrameBufferrows. For a channel withySampling = 3, ZIP uses 16-line chunks; the second chunk starts at file row 16, which is not aligned to the sampling interval. OpenEXR tests sampling with the absolute file row, but computes the destination compact-row offset from the chunk-local row. This shifts later sampled rows upward and leaves the final sampled row unchanged, exposing stale caller-buffer contents if the application later displays, serializes, or transmits the decoded buffer.This is reachable through normal public EXR input. The PoC file is generated with OpenEXR
OutputFileand read with a matching C++InputFile/FrameBufferslice.Affected versions tested
- OpenEXR v3.4.13: reproduced with original, ASAN, and UBSAN builds.
- Upstream
main: reproduced on latest fetchedorigin/maincommit6820342930d35c7c81a6b220c4a0aca9c2532542.Technical details
The relevant invariant is in the generic scanline unpack path. The decoder checks whether a row belongs to a subsampled channel using the absolute row:
int cury = y + decode->chunk.start_y; if ((cury % decc->y_samples) != 0) continue;After that, the destination offset is derived from the chunk-local row
y:cdata += ((y - uls) / decc->y_samples) * decc->user_line_stride;For
ySampling = 3and ZIP's 16-line chunks, file row 18 is accepted because18 % 3 == 0, but its local row inside the second chunk is2, so it is written to compact row2 / 3 == 0relative to that chunk. The expected compact row for file row 18 is the next sampled row after file row 15. This mismatch shifts sampled rows and leaves the last sampled row stale.The same file encoded as
NO_COMPRESSIONis a clean negative control because one-line chunks do not cross a misaligned chunk boundary.PoC
From the OpenEXR v3.4.13 test workspace:
cd "report/51. OpenEXR v3.4.13 Core ySampling ZIP compact FrameBuffer stale row disclosure" ./reproduce.shExpected key output:
v3413_original zip rc=1 BAD k=5 y=15 x=0 got=1180 exp=1150 v3413_original none rc=0 bad=0 v3413_asan zip rc=1 BAD k=5 y=15 x=0 got=1180 exp=1150 v3413_ubsan zip rc=1 BAD k=5 y=15 x=0 got=1180 exp=1150 main_original zip rc=1 BAD k=5 y=15 x=0 got=1180 exp=1150 main_original none rc=0 bad=0 PASS: ySampling ZIP stale row reproduced on v3.4.13 and main original/ASAN/UBSAN; NONE control clean.A separate latest-main check was also run against
origin/maincommit6820342930d35c7c81a6b220c4a0aca9c2532542:zip_rc=1 BAD k=5 y=15 x=0 got=1180 exp=1150 BAD k=10 y=30 x=0 got=-123 exp=1300 none_rc=0 bad=0Impact
A victim application that decodes attacker-controlled ZIP scanline EXR files into a compact subsampled
FrameBufferand then trusts or exposes every sampled output row can disclose stale bytes already present in its caller-owned output buffer and can process pixels from the wrong file rows.The impact should be treated as limited confidentiality exposure plus image data integrity corruption, not memory corruption with control-flow impact.
Files in poc.zip
reproduce.sh— builds/runs the focused probe against local OpenEXR builds.poc_materials/y_sampling3_zip.exr— positive ZIP PoC.poc_materials/y_sampling3_none.exr— cleanNO_COMPRESSIONcontrol.poc_materials/src/make_y_sampling3.cpp— generator for the PoC/control files.poc_materials/src/probe_y_sampling3.cpp— compactFrameBufferreader that detects the row shift and stale final row.