From ba01f9c125802bd5ca87d3763dffd45e1ee3ca54 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 10 Mar 2026 16:36:55 +0100 Subject: [PATCH 1/6] Add check in ReadCompressedTextChunk() for enough data after keyword end --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 271474a7e5..42480a30d1 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1402,26 +1402,31 @@ private void ReadCompressedTextChunk(ImageMetadata baseMetadata, PngMetadata met return; } - int zeroIndex = data.IndexOf((byte)0); - if (zeroIndex is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength) + int keywordEnd = data.IndexOf((byte)0); + if (keywordEnd is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength) { return; } - byte compressionMethod = data[zeroIndex + 1]; + if (keywordEnd < 0 || keywordEnd + 2 > data.Length) + { + return; // Not enough data for keyword + null + compression method. + } + + byte compressionMethod = data[keywordEnd + 1]; if (compressionMethod != 0) { // Only compression method 0 is supported (zlib datastream with deflate compression). return; } - ReadOnlySpan keywordBytes = data[..zeroIndex]; + ReadOnlySpan keywordBytes = data[..keywordEnd]; if (!TryReadTextKeyword(keywordBytes, out string name)) { return; } - ReadOnlySpan compressedData = data[(zeroIndex + 2)..]; + ReadOnlySpan compressedData = data[(keywordEnd + 2)..]; if (this.TryDecompressTextData(compressedData, PngConstants.Encoding, out string? uncompressed) && !TryReadTextChunkMetadata(baseMetadata, name, uncompressed)) From eab4147235ecef95c0ce6893614c852f8ce10e68 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 10 Mar 2026 16:37:30 +0100 Subject: [PATCH 2/6] Add test case for not enough data after keyword end --- .../Formats/Png/PngDecoderTests.Chunks.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index 0b46e7f878..0e62fd12bc 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -92,6 +92,29 @@ public void Decode_TruncatedPhysChunk_ExceptionIsThrown() Assert.Equal("pHYs chunk is too short", exception.Message); } + // https://github.com/SixLabors/ImageSharp/issues/3079 + [Fact] + public void Decode_CompressedTxtChunk_WithTruncatedData_DoesNotThrow() + { + byte[] payload = [137, 80, 78, 71, 13, 10, 26, 10, // PNG signature + 0, 0, 0, 13, // chunk length 13 bytes + 73, 72, 68, 82, // chunk type IHDR + 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, // data + 55, 110, 249, 36, // checksum + 0, 0, 0, 2, // chunk length + 122, 84, 88, 116, // chunk type zTXt + 1, 0, // truncated data + 100, 138, 166, 20, // crc + 0, 0, 0, 10, // chunk length 10 bytes + 73, 68, 65, 84, // chunk type IDAT + 120, 1, 99, 96, 0, 0, 0, 2, 0, 1, // data + 115, 117, 1, 24, // checksum + 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130]; // end chunk + + using MemoryStream stream = new(payload); + using Image image = Image.Load(stream); + } + private static string GetChunkTypeName(uint value) { byte[] data = new byte[4]; From fca50b9678ae00e0859cb49a37f2385dc554624c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 10 Mar 2026 16:44:44 +0100 Subject: [PATCH 3/6] Add check for enough data in ReadInternationalTextChunk() --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 42480a30d1..eadd2a43ce 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1937,6 +1937,11 @@ private void ReadInternationalTextChunk(ImageMetadata metadata, ReadOnlySpan data.Length) + { + return; // Not enough data for keyword + null + flag + method + language. + } + byte compressionFlag = data[zeroIndexKeyword + 1]; if (compressionFlag is not (0 or 1)) { From 457436d970c184dcc74f24901b985e390f8c0257 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 10 Mar 2026 16:45:19 +0100 Subject: [PATCH 4/6] Add test case for not enough data reading InternationalText chunk --- .../Formats/Png/PngDecoderTests.Chunks.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index 0e62fd12bc..d44a9a616f 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -115,6 +115,29 @@ public void Decode_CompressedTxtChunk_WithTruncatedData_DoesNotThrow() using Image image = Image.Load(stream); } + // https://github.com/SixLabors/ImageSharp/issues/3079 + [Fact] + public void Decode_InternationalText_WithTruncatedData_DoesNotThrow() + { + byte[] payload = [137, 80, 78, 71, 13, 10, 26, 10, // PNG signature + 0, 0, 0, 13, // chunk length 13 bytes + 73, 72, 68, 82, // chunk type IHDR + 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, // data + 55, 110, 249, 36, // checksum + 0, 0, 0, 2, // chunk length + 105, 84, 88, 116, // chunk type iTXt + 1, 0, // truncated data + 225, 200, 214, 33, // crc + 0, 0, 0, 10, // chunk length 10 bytes + 73, 68, 65, 84, // chunk type IDAT + 120, 1, 99, 96, 0, 0, 0, 2, 0, 1, // data + 115, 117, 1, 24, // checksum + 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130]; // end chunk + + using MemoryStream stream = new(payload); + using Image image = Image.Load(stream); + } + private static string GetChunkTypeName(uint value) { byte[] data = new byte[4]; From 52c74b563ef6ddf15903c2257c05b2b835dbe87c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 10 Mar 2026 17:22:09 +0100 Subject: [PATCH 5/6] Add check, if translatedKeywordLength is < 0 --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index eadd2a43ce..bca682d77a 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1966,6 +1966,11 @@ private void ReadInternationalTextChunk(ImageMetadata metadata, ReadOnlySpan keywordBytes = data[..zeroIndexKeyword]; From 8d78fe0b080083aff5e6156428b6f730dc52af67 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 10 Mar 2026 17:22:32 +0100 Subject: [PATCH 6/6] Add test case for truncated data after language tag --- .../Formats/Png/PngDecoderTests.Chunks.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index d44a9a616f..823009b68d 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -138,6 +138,29 @@ public void Decode_InternationalText_WithTruncatedData_DoesNotThrow() using Image image = Image.Load(stream); } + // https://github.com/SixLabors/ImageSharp/issues/3079 + [Fact] + public void Decode_InternationalText_WithTruncatedDataAfterLanguageTag_DoesNotThrow() + { + byte[] payload = [137, 80, 78, 71, 13, 10, 26, 10, // PNG signature + 0, 0, 0, 13, // chunk length 13 bytes + 73, 72, 68, 82, // chunk type IHDR + 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, // data + 55, 110, 249, 36, // checksum + 0, 0, 0, 21, // chunk length + 105, 84, 88, 116, // chunk type iTXt + 73, 110, 116, 101, 114, 110, 97, 116, 105, 111, 110, 97, 108, 50, 0, 0, 0, 114, 117, 115, 0, // truncated data after language tag + 225, 200, 214, 33, // crc + 0, 0, 0, 10, // chunk length 10 bytes + 73, 68, 65, 84, // chunk type IDAT + 120, 1, 99, 96, 0, 0, 0, 2, 0, 1, // data + 115, 117, 1, 24, // checksum + 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130]; // end chunk + + using MemoryStream stream = new(payload); + using Image image = Image.Load(stream); + } + private static string GetChunkTypeName(uint value) { byte[] data = new byte[4];