Fix integer overflow and bounds-checking vulnerabilities in EXR decoder#3126
Conversation
Use ulong arithmetic in CalculateBytesPerRow and block size calculations to prevent integer overflow. Add validation for DataWindow dimensions, block size limits, and row offsets outside stream bounds. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@svenclaesson Thank you for this! Just to note
That's not actually true. The decoder was added to main 3 weeks ago |
There was a problem hiding this comment.
Pull request overview
Hardens the EXR decoder against crafted-header denial-of-service cases by validating attacker-controlled dimensions, chunk offsets, and buffer sizing arithmetic during header parsing and scanline decoding.
Changes:
- Add DataWindow sanity checks and compute width/height using wider arithmetic to avoid int overflow.
- Validate scanline chunk offsets against the end of the offset table and the stream length before seeking.
- Promote bytes-per-row/block arithmetic to
ulongand add guards before allocating decode buffers; add targeted security regression tests.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| tests/ImageSharp.Tests/Formats/Exr/ExrDecoderSecurityTests.cs | Adds regression tests covering DataWindow overflow, invalid offset-table seeks, and bytes-per-block overflow scenarios. |
| src/ImageSharp/Formats/Exr/ExrUtils.cs | Changes bytes-per-row computation to ulong to prevent silent uint wraparound. |
| src/ImageSharp/Formats/Exr/ExrEncoderCore.cs | Updates encoder call sites for the CalculateBytesPerRow signature change (currently via casts). |
| src/ImageSharp/Formats/Exr/ExrDecoderCore.cs | Adds header validation for DataWindow, introduces minimum allowed chunk offset, validates chunk offsets before seeking, and guards block-size allocations. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| long width = (long)dataWindow.XMax - dataWindow.XMin + 1; | ||
| long height = (long)dataWindow.YMax - dataWindow.YMin + 1; | ||
| if (width > int.MaxValue || height > int.MaxValue) | ||
| { | ||
| ExrThrowHelper.ThrowInvalidImageContentException("EXR DataWindow dimensions exceed the maximum allowed size."); | ||
| } | ||
|
|
||
| this.Width = (int)width; | ||
| this.Height = (int)height; |
| this.Compression = this.HeaderAttributes.Compression; | ||
| uint rowsPerBlock = ExrUtils.RowsPerBlock(this.Compression); | ||
| long chunkCount = (this.Height + (long)rowsPerBlock - 1) / rowsPerBlock; | ||
| this.MinimumChunkOffset = stream.Position + (chunkCount * sizeof(ulong)); |
Updated. Even if its an old commit it has not made it to main until recently. |
|
@svenclaesson I'm prepping for the major release. Would you like me to complete this PR for you? More than happy too! |
Multiple Denial-of-Service Vulnerabilities in EXR Decoder
Affected Versions: None released — the EXR decoder is on main but has not shipped in any NuGet release as of v3.1.12.
File:
src/ImageSharp/Formats/Exr/ExrDecoderCore.csDescription
Three closely related vulnerabilities in
ExrDecoderCore.csallow a remote attacker to crash any application using ImageSharp's EXR decoder by supplying a crafted.exrfile. All three vulnerabilities stem from missing input validation in the EXR header parsing pipeline and are reachable before any compressed pixel data is read. Because the EXR decoder has not yet shipped in a NuGet release, only consumers building frommainare currently affected.A single fix PR addresses all three vulnerabilities, as they all reside in
ExrDecoderCore.csand share a common root cause: the absence of validated bounds on attacker-controlled integer values parsed from the EXR header.Issue 1 — EXR DataWindow Integer Overflow Produces Negative Image Dimensions
Lines: 600–601
CWE: CWE-190 (Integer Overflow or Wraparound)
CVSS 3.1:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H— 7.5Introduced: 2021-12-28, commit
d3993f7bcon separate branchCode Evidence
With
XMax = 1073741823(2^30 − 1) andXMin = −1073741825(−(2^30 + 1)):Width = (2^30 − 1) − (−(2^30 + 1)) + 1 = 2^31 + 1, which wraps to−2147483647asint32The negative
Widthis passed to theImage<TPixel>constructor, whereGuard.MustBeGreaterThan(width, 0)throwsArgumentOutOfRangeException.Proof of Concept
Recommendation
Additionally, clamp computed dimensions against
DecoderOptions.MaxFrameSizebefore allocating the image buffer.Issue 2 — EXR Row Offset Table — Unvalidated Seek to Attacker-Controlled Stream Position
Lines: 170–175 (scanline path), 243–248 (tile path)
CWE: CWE-20 (Improper Input Validation)
CVSS 3.1:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H— 8.6Introduced: 2022-01-27, commit
fd0c245439on separate branchSeverity Rationale
C:L/I:Lare included beyond the pure DoS path because an offset pointing into the file header causes the decoder to silently produce a successfully decoded image whose pixel data is actually header bytes — a data-integrity failure with no exception. Confidentiality impact is Low and scoped to the decoded image content only.Code Evidence
Three distinct failure modes:
0xFFFFFFFFFFFFFFFFcasts to(long)−1→ArgumentOutOfRangeExceptiononstream.Position.Proof of Concept
Recommendation
Track
headerEndPosition(the stream position after reading the end-of-header sentinel) and reject any offset that points backward into the header or beyond the file.Issue 3 — EXR
bytesPerBlockuint Overflow ChainLines: 142–150 (scanline path), 215–223 (tile path)
CWE: CWE-190 (Integer Overflow or Wraparound)
CVSS 3.1:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H— 7.5Affected Versions: unreleased — present on
mainbranch onlyCode Evidence
With 4 HALF channels (RGBA) and
Width = 2^29:CalculateBytesPerRow = 4 × 2 × 2^29 = 2^32, wraps to0asuintbytesPerBlock = 0 × rowsPerBlock = 0Allocate<byte>(0)succeeds with an empty bufferSpan.Sliceon empty buffer →ArgumentOutOfRangeExceptionProof of Concept
Recommendation
Also validate
this.Widthagainst a reasonable maximum (e.g.,DecoderOptions.MaxFrameSize) before any arithmetic.Consolidated Remediation
All three vulnerabilities are addressed by the same fix PR. The key changes are:
XMax < XMinorYMax < YMininReadBoxInteger; usecheckedarithmetic for dimension computation.offset > headerEndPosition && offset < stream.Lengthbefore seeking.CalculateBytesPerRowandbytesPerBlocktoulong; validate the result fits inintbefore casting toAllocate<byte>.this.Widthandthis.HeightagainstDecoderOptions.MaxFrameSizebefore any downstream arithmetic.