From cf0bb2540f4e8b20b7dfdd50953d5e3eaa036db5 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 8 Sep 2019 22:49:42 +1000 Subject: [PATCH] Fix #1004 --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 38 +++++++++++++++---- .../Formats/Png/Zlib/ZlibInflateStream.cs | 36 ++++++++++++++---- .../Formats/Png/PngDecoderTests.cs | 3 +- tests/ImageSharp.Tests/TestImages.cs | 1 + .../Images/Input/Png/zlib-ztxt-bad-header.png | 3 ++ 5 files changed, 64 insertions(+), 17 deletions(-) create mode 100644 tests/Images/Input/Png/zlib-ztxt-bad-header.png diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 9bc5a5079..a9e588f6e 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -175,11 +175,18 @@ namespace SixLabors.ImageSharp.Formats.Png this.InitializeImage(metadata, out image); } - using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk)) + var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk); + try { - deframeStream.AllocateNewBytes(chunk.Length); + deframeStream.AllocateNewBytes(chunk.Length, true); this.ReadScanlines(deframeStream.CompressedStream, image.Frames.RootFrame, pngMetadata); } + finally + { + // If an invalid Zlib stream is discovered the decoder will throw an exception + // due to the critical nature of the data chunk. + deframeStream.Dispose(); + } break; case PngChunkType.Palette: @@ -924,7 +931,11 @@ namespace SixLabors.ImageSharp.Formats.Png } ReadOnlySpan compressedData = data.Slice(zeroIndex + 2); - metadata.TextData.Add(new PngTextData(name, this.UncompressTextData(compressedData, PngConstants.Encoding), string.Empty, string.Empty)); + + if (this.TryUncompressTextData(compressedData, PngConstants.Encoding, out string uncompressed)) + { + metadata.TextData.Add(new PngTextData(name, uncompressed, string.Empty, string.Empty)); + } } /// @@ -987,7 +998,11 @@ namespace SixLabors.ImageSharp.Formats.Png if (compressionFlag == 1) { ReadOnlySpan compressedData = data.Slice(dataStartIdx); - metadata.TextData.Add(new PngTextData(keyword, this.UncompressTextData(compressedData, PngConstants.TranslatedEncoding), language, translatedKeyword)); + + if (this.TryUncompressTextData(compressedData, PngConstants.TranslatedEncoding, out string uncompressed)) + { + metadata.TextData.Add(new PngTextData(keyword, uncompressed, language, translatedKeyword)); + } } else { @@ -1001,13 +1016,19 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Compressed text data bytes. /// The string encoding to use. - /// A string. - private string UncompressTextData(ReadOnlySpan compressedData, Encoding encoding) + /// The uncompressed value. + /// The . + private bool TryUncompressTextData(ReadOnlySpan compressedData, Encoding encoding, out string value) { using (var memoryStream = new MemoryStream(compressedData.ToArray())) using (var inflateStream = new ZlibInflateStream(memoryStream, () => 0)) { - inflateStream.AllocateNewBytes(compressedData.Length); + if (!inflateStream.AllocateNewBytes(compressedData.Length, false)) + { + value = null; + return false; + } + var uncompressedBytes = new List(); // Note: this uses the a buffer which is only 4 bytes long to read the stream, maybe allocating a larger buffer makes sense here. @@ -1018,7 +1039,8 @@ namespace SixLabors.ImageSharp.Formats.Png bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length); } - return encoding.GetString(uncompressedBytes.ToArray()); + value = encoding.GetString(uncompressedBytes.ToArray()); + return true; } } diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs index 405eeafeb..df0e72332 100644 --- a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs +++ b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs @@ -87,13 +87,17 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// Adds new bytes from a frame found in the original stream /// /// blabla - public void AllocateNewBytes(int bytes) + /// Whether the chunk to be inflated is a critical chunk. + /// The . + public bool AllocateNewBytes(int bytes, bool isCriticalChunk) { this.currentDataRemaining = bytes; if (this.compressedStream is null) { - this.InitializeInflateStream(); + return this.InitializeInflateStream(isCriticalChunk); } + + return true; } /// @@ -197,7 +201,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.isDisposed = true; } - private void InitializeInflateStream() + private bool InitializeInflateStream(bool isCriticalChunk) { // Read the zlib header : http://tools.ietf.org/html/rfc1950 // CMF(Compression Method and flags) @@ -215,7 +219,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.currentDataRemaining -= 2; if (cmf == -1 || flag == -1) { - return; + return false; } if ((cmf & 0x0F) == 8) @@ -225,14 +229,28 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib if (cinfo > 7) { - // Values of CINFO above 7 are not allowed in RFC1950. - // CINFO is not defined in this specification for CM not equal to 8. - throw new ImageFormatException($"Invalid window size for ZLIB header: cinfo={cinfo}"); + if (isCriticalChunk) + { + // Values of CINFO above 7 are not allowed in RFC1950. + // CINFO is not defined in this specification for CM not equal to 8. + throw new ImageFormatException($"Invalid window size for ZLIB header: cinfo={cinfo}"); + } + else + { + return false; + } } } else { - throw new ImageFormatException($"Bad method for ZLIB header: cmf={cmf}"); + if (isCriticalChunk) + { + throw new ImageFormatException($"Bad method for ZLIB header: cmf={cmf}"); + } + else + { + return false; + } } // The preset dictionary. @@ -247,6 +265,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib // Initialize the deflate Stream. this.compressedStream = new DeflateStream(this, CompressionMode.Decompress, true); + + return true; } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 2e9fd7481..91b1ef2c1 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -40,7 +40,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png TestImages.Png.GrayAlpha8Bit, TestImages.Png.Gray1BitTrans, TestImages.Png.Bad.ZlibOverflow, - TestImages.Png.Bad.ZlibOverflow2 + TestImages.Png.Bad.ZlibOverflow2, + TestImages.Png.Bad.ZlibZtxtBadHeader, }; public static readonly string[] TestImages48Bpp = diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index e95ce0907..163d09bdd 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -90,6 +90,7 @@ namespace SixLabors.ImageSharp.Tests public const string CorruptedChunk = "Png/big-corrupted-chunk.png"; public const string ZlibOverflow = "Png/zlib-overflow.png"; public const string ZlibOverflow2 = "Png/zlib-overflow2.png"; + public const string ZlibZtxtBadHeader = "Png/zlib-ztxt-bad-header.png"; } public static readonly string[] All = diff --git a/tests/Images/Input/Png/zlib-ztxt-bad-header.png b/tests/Images/Input/Png/zlib-ztxt-bad-header.png new file mode 100644 index 000000000..0eb37aab8 --- /dev/null +++ b/tests/Images/Input/Png/zlib-ztxt-bad-header.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce623255656921d491b5c389cd46931fbd6024575b87522c55d67a496dd761f0 +size 22781