diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 179bf38ba8..a96c53c104 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -120,6 +120,11 @@ internal sealed class PngDecoderCore : IImageDecoderInternals /// private readonly PngCrcChunkHandling pngCrcChunkHandling; + /// + /// The maximum memory in bytes that a zTXt, sPLT, iTXt, iCCP, or unknown chunk can occupy when decompressed. + /// + private readonly int maxUncompressedLength; + /// /// Initializes a new instance of the class. /// @@ -132,6 +137,7 @@ public PngDecoderCore(PngDecoderOptions options) this.skipMetadata = options.GeneralOptions.SkipMetadata; this.memoryAllocator = this.configuration.MemoryAllocator; this.pngCrcChunkHandling = options.PngCrcChunkHandling; + this.maxUncompressedLength = options.MaxUncompressedAncillaryChunkSizeBytes; } internal PngDecoderCore(PngDecoderOptions options, bool colorMetadataOnly) @@ -143,6 +149,7 @@ internal PngDecoderCore(PngDecoderOptions options, bool colorMetadataOnly) this.configuration = options.GeneralOptions.Configuration; this.memoryAllocator = this.configuration.MemoryAllocator; this.pngCrcChunkHandling = options.PngCrcChunkHandling; + this.maxUncompressedLength = options.MaxUncompressedAncillaryChunkSizeBytes; } /// @@ -596,23 +603,7 @@ private static void ReadGammaChunk(PngMetadata pngMetadata, ReadOnlySpan d private void InitializeImage(ImageMetadata metadata, FrameControl frameControl, out Image image) where TPixel : unmanaged, IPixel { - // When ignoring data CRCs, we can't use the image constructor that leaves the buffer uncleared. - if (this.pngCrcChunkHandling is PngCrcChunkHandling.IgnoreData or PngCrcChunkHandling.IgnoreAll) - { - image = new Image( - this.configuration, - this.header.Width, - this.header.Height, - metadata); - } - else - { - image = Image.CreateUninitialized( - this.configuration, - this.header.Width, - this.header.Height, - metadata); - } + image = new Image(this.configuration, this.header.Width, this.header.Height, metadata); PngFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetPngMetadata(); frameMetadata.FromChunk(in frameControl); @@ -1572,7 +1563,7 @@ private void ReadColorProfileChunk(ImageMetadata metadata, ReadOnlySpan da ReadOnlySpan compressedData = data[(zeroIndex + 2)..]; - if (this.TryDecompressZlibData(compressedData, out byte[] iccpProfileBytes)) + if (this.TryDecompressZlibData(compressedData, this.maxUncompressedLength, out byte[] iccpProfileBytes)) { metadata.IccProfile = new IccProfile(iccpProfileBytes); } @@ -1582,9 +1573,10 @@ private void ReadColorProfileChunk(ImageMetadata metadata, ReadOnlySpan da /// Tries to decompress zlib compressed data. /// /// The compressed data. + /// The maximum uncompressed length. /// The uncompressed bytes array. /// True, if de-compressing was successful. - private unsafe bool TryDecompressZlibData(ReadOnlySpan compressedData, out byte[] uncompressedBytesArray) + private unsafe bool TryDecompressZlibData(ReadOnlySpan compressedData, int maxLength, out byte[] uncompressedBytesArray) { fixed (byte* compressedDataBase = compressedData) { @@ -1604,6 +1596,12 @@ private unsafe bool TryDecompressZlibData(ReadOnlySpan compressedData, out int bytesRead = inflateStream.CompressedStream.Read(destUncompressedData, 0, destUncompressedData.Length); while (bytesRead != 0) { + if (memoryStreamOutput.Length > maxLength) + { + uncompressedBytesArray = Array.Empty(); + return false; + } + memoryStreamOutput.Write(destUncompressedData[..bytesRead]); bytesRead = inflateStream.CompressedStream.Read(destUncompressedData, 0, destUncompressedData.Length); } @@ -1746,7 +1744,7 @@ private void ReadInternationalTextChunk(ImageMetadata metadata, ReadOnlySpanThe . private bool TryDecompressTextData(ReadOnlySpan compressedData, Encoding encoding, [NotNullWhen(true)] out string? value) { - if (this.TryDecompressZlibData(compressedData, out byte[] uncompressedData)) + if (this.TryDecompressZlibData(compressedData, this.maxUncompressedLength, out byte[] uncompressedData)) { value = encoding.GetString(uncompressedData); return true; diff --git a/src/ImageSharp/Formats/Png/PngDecoderOptions.cs b/src/ImageSharp/Formats/Png/PngDecoderOptions.cs index ab6ba4770e..abfa4b1da8 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderOptions.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderOptions.cs @@ -15,4 +15,10 @@ public sealed class PngDecoderOptions : ISpecializedDecoderOptions /// Gets a value indicating how to handle validation of any CRC (Cyclic Redundancy Check) data within the encoded PNG. /// public PngCrcChunkHandling PngCrcChunkHandling { get; init; } = PngCrcChunkHandling.IgnoreNonCritical; + + /// + /// Gets the maximum memory in bytes that a zTXt, sPLT, iTXt, iCCP, or unknown chunk can occupy when decompressed. + /// Defaults to 8MB + /// + public int MaxUncompressedAncillaryChunkSizeBytes { get; init; } = 8 * 1024 * 1024; // 8MB } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index b9f6075fb8..9b165526eb 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -672,4 +672,23 @@ public void Decode_Issue2666() string path = Path.GetFullPath(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Png.Issue2666)); using Image image = Image.Load(path); } + + [Theory] + + [InlineData(TestImages.Png.Bad.BadZTXT)] + [InlineData(TestImages.Png.Bad.BadZTXT2)] + public void Decode_BadZTXT(string file) + { + string path = Path.GetFullPath(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, file)); + using Image image = Image.Load(path); + } + + [Theory] + [InlineData(TestImages.Png.Bad.BadZTXT)] + [InlineData(TestImages.Png.Bad.BadZTXT2)] + public void Info_BadZTXT(string file) + { + string path = Path.GetFullPath(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, file)); + _ = Image.Identify(path); + } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index e72f6fad8d..00f7370f31 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -186,8 +186,10 @@ public static class Bad // Invalid color type. public const string ColorTypeOne = "Png/xc1n0g08.png"; public const string ColorTypeNine = "Png/xc9n2c08.png"; - public const string FlagOfGermany0000016446 = "Png/issues/flag_of_germany-0000016446.png"; + + public const string BadZTXT = "Png/issues/bad-ztxt.png"; + public const string BadZTXT2 = "Png/issues/bad-ztxt2.png"; } } diff --git a/tests/Images/Input/Png/issues/bad-ztxt.png b/tests/Images/Input/Png/issues/bad-ztxt.png new file mode 100644 index 0000000000..710f888d0b --- /dev/null +++ b/tests/Images/Input/Png/issues/bad-ztxt.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:132a70cf0ac458a55cf4a44f4c6c025587491d304595835959955de6682fa472 +size 3913750 diff --git a/tests/Images/Input/Png/issues/bad-ztxt2.png b/tests/Images/Input/Png/issues/bad-ztxt2.png new file mode 100644 index 0000000000..958c00e3f0 --- /dev/null +++ b/tests/Images/Input/Png/issues/bad-ztxt2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:778a5fc8e915d79e9f55e58c6e4f646ae55dd7e866e65960754cb67a2b445987 +size 93