Skip to content

Fix negative-count memcpy in EXR decoder#216

Merged
eustas merged 2 commits intogoogle:mainfrom
sharadboni:fix/security-exr-negative-memcpy
May 8, 2026
Merged

Fix negative-count memcpy in EXR decoder#216
eustas merged 2 commits intogoogle:mainfrom
sharadboni:fix/security-exr-negative-memcpy

Conversation

@sharadboni
Copy link
Copy Markdown
Contributor

Security fix

Negative-count memcpy in EXR decoder (lib/extras/dec/exr.cc)

In DecodeImageEXR, when the EXR data window and display window have no horizontal overlap, the intersection produces exr_x2 < exr_x1 (after std::max/std::min). The expression (exr_x2 - exr_x1 + 1) then wraps to a huge size_t and is passed to memcpy, causing a massive out-of-bounds read/write.

Refactor (per maintainer review on #213)

  • Hoist exr_x1 and exr_x2 above the exr_y loop — they don't depend on exr_y.
  • Compute exr_x_span = max(0, exr_x2 - exr_x1 + 1).
  • Skip the entire exr_y loop when exr_x_span == 0 (no overlap → no work to do).
  • Use exr_x_span directly in both memcpy calls (color + extra channels).

This was originally bundled with an unrelated jpegli encoder fix in #213; split out per maintainer request since this fix is also useful for JPEG XL.

@eustas
Copy link
Copy Markdown
Collaborator

eustas commented May 6, 2026

Please rebase. Please consider submitting similar PR for libjxl.

In DecodeImageEXR, when the EXR data window and display window have no
horizontal overlap, exr_x2 < exr_x1, so (exr_x2 - exr_x1 + 1) wraps to a
huge size_t and produces an out-of-bounds memcpy.

Hoist exr_x1 and exr_x2 above the exr_y loop (they don't depend on the
y-iteration), compute exr_x_span = max(0, exr_x2 - exr_x1 + 1), and skip
the entire y-loop when exr_x_span is zero.
@sharadboni sharadboni force-pushed the fix/security-exr-negative-memcpy branch from 7b8b9f4 to 9222fd3 Compare May 7, 2026 17:52
@sharadboni
Copy link
Copy Markdown
Contributor Author

Done on both counts.

  • Rebased on current main (de-jxl-isation rename), force-pushed (head now 9222fd3). PR is mergeable again.
  • Sister PR for libjxl: libjxl/libjxl#4758. Identical code shape, just JXL_RESTRICT since libjxl hasn't been de-jxl-ified.

@eustas eustas merged commit 7a674c3 into google:main May 8, 2026
72 of 79 checks passed
sharadboni added a commit to sharadboni/jpegli that referenced this pull request May 8, 2026
In DecodeImageEXR, the OpenEXR framebuffer base pointer for extra
channels was computed using extraPixelBytes (sum of all extra
channels' per-pixel sizes), but each per-channel Slice was configured
with the channel's individual size. With dataWindow.min.x > 0 (or
start_y > 0) and multiple extra channels, OpenEXR's pixel address
resolution

    base + x * size + y * size * row_size

underflows the input_extra_rows allocation by
data_min_x * (extraPixelBytes - size) bytes for channel 0's first
pixel, and a similar term for higher chunks. The result is a
heap-buffer-underflow write of attacker-controlled bytes (the EXR
extra-channel pixel data) at a controllable offset before the
allocated buffer.

Fix: keep input_extra_rows.data() unmodified as the per-channel
section base, and compute a separate ch_base_ptr per channel using
that channel's own size. With this, each channel's slice resolves
addresses correctly within its own section.

The negative-count memcpy at exr.cc:365 (also originally addressed
by this branch) was merged separately via google#216 and is unaffected by
this change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants