diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index d9e0f1e08f..ec3ebfa1dc 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -68,7 +68,9 @@ jobs: steps: - name: Install libgdi+, which is required for tests running on ubuntu if: ${{ contains(matrix.options.os, 'ubuntu') }} - run: sudo apt-get -y install libgdiplus libgif-dev libglib2.0-dev libcairo2-dev libtiff-dev libexif-dev + run: | + sudo apt-get update + sudo apt-get -y install libgdiplus libgif-dev libglib2.0-dev libcairo2-dev libtiff-dev libexif-dev - name: Git Config shell: bash diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index db9aca0b08..de95bf01b2 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -18,7 +18,9 @@ jobs: steps: - name: Install libgdi+, which is required for tests running on ubuntu - run: sudo apt-get -y install libgdiplus libgif-dev libglib2.0-dev libcairo2-dev libtiff-dev libexif-dev + run: | + sudo apt-get update + sudo apt-get -y install libgdiplus libgif-dev libglib2.0-dev libcairo2-dev libtiff-dev libexif-dev - name: Git Config shell: bash diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 4477df35cd..455227e889 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -54,7 +54,7 @@ public void Encode(Image image, Stream stream, CancellationToken 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 cef8396d4b..f6dad651b7 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -125,6 +125,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. /// @@ -746,7 +751,11 @@ private void ReadScanlines( 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) @@ -800,7 +809,7 @@ private void DecodePixelData( int bytesRead = compressedStream.Read(scanSpan, currentRowBytesRead, bytesPerFrameScanline - currentRowBytesRead); if (bytesRead <= 0) { - return; + goto EXIT; } currentRowBytesRead += bytesRead; @@ -845,6 +854,7 @@ private void DecodePixelData( } EXIT: + this.hasImageData = true; blendMemory?.Dispose(); } @@ -903,7 +913,7 @@ private void DecodeInterlacedPixelData( int bytesRead = compressedStream.Read(this.scanline.GetSpan(), currentRowBytesRead, bytesPerInterlaceScanline - currentRowBytesRead); if (bytesRead <= 0) { - return; + goto EXIT; } currentRowBytesRead += bytesRead; @@ -976,6 +986,7 @@ private void DecodeInterlacedPixelData( } 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 40009f525b..0351798b5f 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -374,9 +374,6 @@ public int EncodeAlphaImageData(Buffer2DRegion frame, IMemoryOwn /// 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 781b8246ff..21e1b55cfc 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs @@ -217,7 +217,7 @@ private WebpImageInfo ReadVp8Info(BufferedReadStream stream, ImageMetadata metad else { // Ignore unknown chunks. - uint chunkSize = ReadChunkSize(stream, buffer); + uint chunkSize = ReadChunkSize(stream, buffer, false); stream.Skip((int)chunkSize); } } @@ -500,9 +500,10 @@ private static WebpChunkType ReadChunkType(BufferedReadStream stream, Span /// /// 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) { @@ -510,7 +511,13 @@ private static uint ReadChunkSize(BufferedReadStream stream, Span buffer) 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 5d904380bf..4b273e6162 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs @@ -117,6 +117,11 @@ public void Encode(Image image, Stream stream, CancellationToken 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 11af57e39f..5a5dd9aaa7 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -705,10 +705,20 @@ public void Decode_BadPalette(string file) [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 29362105db..d81cd20849 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -549,4 +549,14 @@ public void Identify_VerifyRatio(string imagePath) 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 6463f799b5..5a7c2e66ea 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -160,6 +160,9 @@ public static class Png // 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"; @@ -856,6 +859,7 @@ public static class Lossy 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"; } } 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