diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 34028c2f83..c9f4258feb 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -58,7 +58,7 @@ internal sealed unsafe partial class JpegEncoderCore Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - if (image.Width >= JpegConstants.MaxLength || image.Height >= JpegConstants.MaxLength) + if (image.Width > JpegConstants.MaxLength || image.Height > JpegConstants.MaxLength) { JpegThrowHelper.ThrowDimensionsTooLarge(image.Width, image.Height); } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index b0ec73d2a1..0971c3ecfc 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -131,6 +131,11 @@ internal sealed class PngDecoderCore : ImageDecoderCore /// private readonly int maxUncompressedLength; + /// + /// A value indicating whether the image data has been read. + /// + private bool hasImageData; + /// /// Initializes a new instance of the class. /// @@ -749,7 +754,11 @@ internal sealed class PngDecoderCore : ImageDecoderCore where TPixel : unmanaged, IPixel { using ZlibInflateStream inflateStream = new(this.currentStream, getData); - inflateStream.AllocateNewBytes(chunkLength, true); + if (!inflateStream.AllocateNewBytes(chunkLength, !this.hasImageData)) + { + return; + } + DeflateStream dataStream = inflateStream.CompressedStream!; if (this.header.InterlaceMethod is PngInterlaceMode.Adam7) @@ -803,7 +812,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore int bytesRead = compressedStream.Read(scanSpan, currentRowBytesRead, bytesPerFrameScanline - currentRowBytesRead); if (bytesRead <= 0) { - return; + goto EXIT; } currentRowBytesRead += bytesRead; @@ -848,6 +857,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore } EXIT: + this.hasImageData = true; blendMemory?.Dispose(); } @@ -906,7 +916,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore int bytesRead = compressedStream.Read(this.scanline.GetSpan(), currentRowBytesRead, bytesPerInterlaceScanline - currentRowBytesRead); if (bytesRead <= 0) { - return; + goto EXIT; } currentRowBytesRead += bytesRead; @@ -979,6 +989,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore } EXIT: + this.hasImageData = true; blendMemory?.Dispose(); } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index f088448391..1864e539c1 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -371,9 +371,6 @@ internal class Vp8LEncoder : IDisposable /// The input image height. private void WriteImageSize(int inputImgWidth, int inputImgHeight) { - Guard.MustBeLessThan(inputImgWidth, WebpConstants.MaxDimension, nameof(inputImgWidth)); - Guard.MustBeLessThan(inputImgHeight, WebpConstants.MaxDimension, nameof(inputImgHeight)); - uint width = (uint)inputImgWidth - 1; uint height = (uint)inputImgHeight - 1; diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs index 9ca6e2bee3..51379a32ae 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs @@ -220,7 +220,7 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable else { // Ignore unknown chunks. - uint chunkSize = ReadChunkSize(stream, buffer); + uint chunkSize = ReadChunkSize(stream, buffer, false); stream.Skip((int)chunkSize); } } @@ -498,9 +498,10 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable /// /// The stream to decode from. /// Temporary buffer. + /// If true, the chunk size is required to be read, otherwise it can be skipped. /// The chunk size in bytes. /// Invalid data. - private static uint ReadChunkSize(BufferedReadStream stream, Span buffer) + private static uint ReadChunkSize(BufferedReadStream stream, Span buffer, bool required = true) { if (stream.Read(buffer, 0, 4) == 4) { @@ -508,7 +509,13 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1; } - throw new ImageFormatException("Invalid Webp data."); + if (required) + { + throw new ImageFormatException("Invalid Webp data."); + } + + // Return the size of the remaining data in the stream. + return (uint)(stream.Length - stream.Position); } /// diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs index b3270786d7..788a90a829 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs @@ -132,6 +132,11 @@ internal sealed class WebpEncoderCore Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); + if (image.Width > WebpConstants.MaxDimension || image.Height > WebpConstants.MaxDimension) + { + WebpThrowHelper.ThrowDimensionsTooLarge(image.Width, image.Height); + } + bool lossless; if (this.fileFormat is not null) { diff --git a/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs b/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs index c633c52738..d730953829 100644 --- a/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs +++ b/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs @@ -18,4 +18,7 @@ internal static class WebpThrowHelper [DoesNotReturn] public static void ThrowInvalidImageDimensions(string errorMessage) => throw new InvalidImageContentException(errorMessage); + + [DoesNotReturn] + public static void ThrowDimensionsTooLarge(int width, int height) => throw new ImageFormatException($"Image is too large to encode at {width}x{height} for WEBP format."); } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 9f3c5f6828..4d058e54e8 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -719,10 +719,20 @@ public partial class PngDecoderTests [Theory] [WithFile(TestImages.Png.Issue2752, PixelTypes.Rgba32)] public void CanDecodeJustOneFrame(TestImageProvider provider) - where TPixel : unmanaged, IPixel + where TPixel : unmanaged, IPixel { DecoderOptions options = new() { MaxFrames = 1 }; using Image image = provider.GetImage(PngDecoder.Instance, options); Assert.Equal(1, image.Frames.Count); } + + [Theory] + [WithFile(TestImages.Png.Issue2924, PixelTypes.Rgba32)] + public void CanDecode_Issue2924(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder.Instance); + image.DebugSave(provider); + image.CompareToReferenceOutput(provider); + } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index adabb727d8..660ab2e666 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -549,4 +549,14 @@ public class WebpDecoderTests Assert.Equal(37.8, meta.VerticalResolution); Assert.Equal(PixelResolutionUnit.PixelsPerCentimeter, meta.ResolutionUnits); } + + [Theory] + [WithFile(Lossy.Issue2925, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Issue2925(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder.Instance); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index a5336e52fb..8b5529ac1a 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -160,6 +160,9 @@ public static class TestImages // Issue 2752: https://github.com/SixLabors/ImageSharp/issues/2752 public const string Issue2752 = "Png/issues/Issue_2752.png"; + // Issue 2924: https://github.com/SixLabors/ImageSharp/issues/2924 + public const string Issue2924 = "Png/issues/Issue_2924.png"; + public static class Bad { public const string MissingDataChunk = "Png/xdtn0g01.png"; @@ -871,6 +874,7 @@ public static class TestImages public const string Issue2763 = "Webp/issues/Issue2763.png"; public const string Issue2801 = "Webp/issues/Issue2801.webp"; public const string Issue2866 = "Webp/issues/Issue2866.webp"; + public const string Issue2925 = "Webp/issues/Issue2925.webp"; } public const string AlphaBlend = "Webp/alpha-blend.webp"; diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 263df8f3a6..994aa670c7 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -59,11 +59,6 @@ public static class TestImageExtensions bool appendSourceFileOrDescription = true, IImageEncoder encoder = null) { - if (TestEnvironment.RunsWithCodeCoverage) - { - return image; - } - provider.Utility.SaveTestOutputFile( image, extension, @@ -112,11 +107,6 @@ public static class TestImageExtensions Func predicate = null) where TPixel : unmanaged, IPixel { - if (TestEnvironment.RunsWithCodeCoverage) - { - return image; - } - provider.Utility.SaveTestOutputFileMultiFrame( image, extension, @@ -298,24 +288,23 @@ public static class TestImageExtensions appendPixelTypeToFileName, predicate: predicate); - using (Image debugImage = GetDebugOutputImageMultiFrame( + using Image debugImage = GetDebugOutputImageMultiFrame( provider, image.Frames.Count, testOutputDetails, extension, appendPixelTypeToFileName, - predicate: predicate)) + predicate: predicate); - using (Image referenceImage = GetReferenceOutputImageMultiFrame( + using Image referenceImage = GetReferenceOutputImageMultiFrame( provider, image.Frames.Count, testOutputDetails, extension, appendPixelTypeToFileName, - predicate: predicate)) - { - comparer.VerifySimilarity(referenceImage, debugImage); - } + predicate: predicate); + + comparer.VerifySimilarity(referenceImage, debugImage); return image; } diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/CanDecode_Issue2924_Rgba32_Issue_2924.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/CanDecode_Issue2924_Rgba32_Issue_2924.png new file mode 100644 index 0000000000..023f346e03 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/CanDecode_Issue2924_Rgba32_Issue_2924.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4347cd89196c09496288724afdd876b227063149bba33615c338ebb474a0cb19 +size 47260 diff --git a/tests/Images/Input/Png/issues/Issue_2924.png b/tests/Images/Input/Png/issues/Issue_2924.png new file mode 100644 index 0000000000..0454642190 --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_2924.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd366a2de522041c706729b01a6030df6c82a4e87ea3509cc78a47486f097044 +size 49376 diff --git a/tests/Images/Input/Webp/issues/Issue2925.webp b/tests/Images/Input/Webp/issues/Issue2925.webp new file mode 100644 index 0000000000..414a06caad --- /dev/null +++ b/tests/Images/Input/Webp/issues/Issue2925.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e12207babf122af7a62246938c2e78faa0d3f730edb3182f4f9d6adae6bfc602 +size 262144