diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 4fb769fbec..d4b3828453 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -651,7 +651,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore frameMetadata.FromChunk(in frameControl); this.bytesPerPixel = this.CalculateBytesPerPixel(); - this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1; + this.bytesPerScanline = CalculateScanlineLength(this.header.Width, this.header.BitDepth, this.bytesPerPixel) + 1; this.bytesPerSample = 1; if (this.header.BitDepth >= 8) { @@ -736,21 +736,29 @@ internal sealed class PngDecoderCore : ImageDecoderCore /// Calculates the scanline length. /// /// The width of the row. + /// The number of bits per sample. + /// The number of bytes per pixel. /// /// The representing the length. /// - private int CalculateScanlineLength(int width) + internal static int CalculateScanlineLength(int width, int bitDepth, int bytesPerPixel) { - int mod = this.header.BitDepth == 16 ? 16 : 8; - int scanlineLength = width * this.header.BitDepth * this.bytesPerPixel; + int mod = bitDepth == 16 ? 16 : 8; + long scanlineLength = (long)width * bitDepth * bytesPerPixel; - int amount = scanlineLength % mod; + long amount = scanlineLength % mod; if (amount != 0) { scanlineLength += mod - amount; } - return scanlineLength / mod; + scanlineLength /= mod; + if (scanlineLength >= int.MaxValue) + { + PngThrowHelper.ThrowInvalidImageContentException("PNG scanline length exceeds the supported maximum."); + } + + return (int)scanlineLength; } /// @@ -870,7 +878,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore while (currentRow < height) { cancellationToken.ThrowIfCancellationRequested(); - int bytesPerFrameScanline = this.CalculateScanlineLength((int)frameControl.Width) + 1; + int bytesPerFrameScanline = CalculateScanlineLength((int)frameControl.Width, this.header.BitDepth, this.bytesPerPixel) + 1; Span scanSpan = this.scanline.GetSpan()[..bytesPerFrameScanline]; Span prevSpan = this.previousScanline.GetSpan()[..bytesPerFrameScanline]; @@ -1001,7 +1009,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore continue; } - int bytesPerInterlaceScanline = this.CalculateScanlineLength(numColumns) + 1; + int bytesPerInterlaceScanline = CalculateScanlineLength(numColumns, this.header.BitDepth, this.bytesPerPixel) + 1; while (currentRow < endRow) { diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderCoreTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderCoreTests.cs new file mode 100644 index 0000000000..0b442f8834 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderCoreTests.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Png; + +namespace SixLabors.ImageSharp.Tests.Formats.Png; + +[Trait("Format", "Png")] +public class PngDecoderCoreTests +{ + [Fact] + public void CalculateScanlineLength_WithLargeGrayscaleWidth_ReturnsExpectedLength() + { + int length = PngDecoderCore.CalculateScanlineLength(536_870_913, 8, 1); + + Assert.Equal(536_870_913, length); + } +}