Skip to content

Commit 0942689

Browse files
Merge pull request #2681 from SixLabors/js/png-dimensions-fix
Limit ancillary PNG chunk size.
2 parents 3806fd2 + edc87ad commit 0942689

File tree

6 files changed

+52
-21
lines changed

6 files changed

+52
-21
lines changed

src/ImageSharp/Formats/Png/PngDecoderCore.cs

+18-20
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
120120
/// </summary>
121121
private readonly PngCrcChunkHandling pngCrcChunkHandling;
122122

123+
/// <summary>
124+
/// The maximum memory in bytes that a zTXt, sPLT, iTXt, iCCP, or unknown chunk can occupy when decompressed.
125+
/// </summary>
126+
private readonly int maxUncompressedLength;
127+
123128
/// <summary>
124129
/// Initializes a new instance of the <see cref="PngDecoderCore"/> class.
125130
/// </summary>
@@ -132,6 +137,7 @@ public PngDecoderCore(PngDecoderOptions options)
132137
this.skipMetadata = options.GeneralOptions.SkipMetadata;
133138
this.memoryAllocator = this.configuration.MemoryAllocator;
134139
this.pngCrcChunkHandling = options.PngCrcChunkHandling;
140+
this.maxUncompressedLength = options.MaxUncompressedAncillaryChunkSizeBytes;
135141
}
136142

137143
internal PngDecoderCore(PngDecoderOptions options, bool colorMetadataOnly)
@@ -143,6 +149,7 @@ internal PngDecoderCore(PngDecoderOptions options, bool colorMetadataOnly)
143149
this.configuration = options.GeneralOptions.Configuration;
144150
this.memoryAllocator = this.configuration.MemoryAllocator;
145151
this.pngCrcChunkHandling = options.PngCrcChunkHandling;
152+
this.maxUncompressedLength = options.MaxUncompressedAncillaryChunkSizeBytes;
146153
}
147154

148155
/// <inheritdoc/>
@@ -596,23 +603,7 @@ private static void ReadGammaChunk(PngMetadata pngMetadata, ReadOnlySpan<byte> d
596603
private void InitializeImage<TPixel>(ImageMetadata metadata, FrameControl frameControl, out Image<TPixel> image)
597604
where TPixel : unmanaged, IPixel<TPixel>
598605
{
599-
// When ignoring data CRCs, we can't use the image constructor that leaves the buffer uncleared.
600-
if (this.pngCrcChunkHandling is PngCrcChunkHandling.IgnoreData or PngCrcChunkHandling.IgnoreAll)
601-
{
602-
image = new Image<TPixel>(
603-
this.configuration,
604-
this.header.Width,
605-
this.header.Height,
606-
metadata);
607-
}
608-
else
609-
{
610-
image = Image.CreateUninitialized<TPixel>(
611-
this.configuration,
612-
this.header.Width,
613-
this.header.Height,
614-
metadata);
615-
}
606+
image = new Image<TPixel>(this.configuration, this.header.Width, this.header.Height, metadata);
616607

617608
PngFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetPngMetadata();
618609
frameMetadata.FromChunk(in frameControl);
@@ -1572,7 +1563,7 @@ private void ReadColorProfileChunk(ImageMetadata metadata, ReadOnlySpan<byte> da
15721563

15731564
ReadOnlySpan<byte> compressedData = data[(zeroIndex + 2)..];
15741565

1575-
if (this.TryDecompressZlibData(compressedData, out byte[] iccpProfileBytes))
1566+
if (this.TryDecompressZlibData(compressedData, this.maxUncompressedLength, out byte[] iccpProfileBytes))
15761567
{
15771568
metadata.IccProfile = new IccProfile(iccpProfileBytes);
15781569
}
@@ -1582,9 +1573,10 @@ private void ReadColorProfileChunk(ImageMetadata metadata, ReadOnlySpan<byte> da
15821573
/// Tries to decompress zlib compressed data.
15831574
/// </summary>
15841575
/// <param name="compressedData">The compressed data.</param>
1576+
/// <param name="maxLength">The maximum uncompressed length.</param>
15851577
/// <param name="uncompressedBytesArray">The uncompressed bytes array.</param>
15861578
/// <returns>True, if de-compressing was successful.</returns>
1587-
private unsafe bool TryDecompressZlibData(ReadOnlySpan<byte> compressedData, out byte[] uncompressedBytesArray)
1579+
private unsafe bool TryDecompressZlibData(ReadOnlySpan<byte> compressedData, int maxLength, out byte[] uncompressedBytesArray)
15881580
{
15891581
fixed (byte* compressedDataBase = compressedData)
15901582
{
@@ -1604,6 +1596,12 @@ private unsafe bool TryDecompressZlibData(ReadOnlySpan<byte> compressedData, out
16041596
int bytesRead = inflateStream.CompressedStream.Read(destUncompressedData, 0, destUncompressedData.Length);
16051597
while (bytesRead != 0)
16061598
{
1599+
if (memoryStreamOutput.Length > maxLength)
1600+
{
1601+
uncompressedBytesArray = Array.Empty<byte>();
1602+
return false;
1603+
}
1604+
16071605
memoryStreamOutput.Write(destUncompressedData[..bytesRead]);
16081606
bytesRead = inflateStream.CompressedStream.Read(destUncompressedData, 0, destUncompressedData.Length);
16091607
}
@@ -1746,7 +1744,7 @@ private void ReadInternationalTextChunk(ImageMetadata metadata, ReadOnlySpan<byt
17461744
/// <returns>The <see cref="bool"/>.</returns>
17471745
private bool TryDecompressTextData(ReadOnlySpan<byte> compressedData, Encoding encoding, [NotNullWhen(true)] out string? value)
17481746
{
1749-
if (this.TryDecompressZlibData(compressedData, out byte[] uncompressedData))
1747+
if (this.TryDecompressZlibData(compressedData, this.maxUncompressedLength, out byte[] uncompressedData))
17501748
{
17511749
value = encoding.GetString(uncompressedData);
17521750
return true;

src/ImageSharp/Formats/Png/PngDecoderOptions.cs

+6
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,10 @@ public sealed class PngDecoderOptions : ISpecializedDecoderOptions
1515
/// Gets a value indicating how to handle validation of any CRC (Cyclic Redundancy Check) data within the encoded PNG.
1616
/// </summary>
1717
public PngCrcChunkHandling PngCrcChunkHandling { get; init; } = PngCrcChunkHandling.IgnoreNonCritical;
18+
19+
/// <summary>
20+
/// Gets the maximum memory in bytes that a zTXt, sPLT, iTXt, iCCP, or unknown chunk can occupy when decompressed.
21+
/// Defaults to 8MB
22+
/// </summary>
23+
public int MaxUncompressedAncillaryChunkSizeBytes { get; init; } = 8 * 1024 * 1024; // 8MB
1824
}

tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

+19
Original file line numberDiff line numberDiff line change
@@ -672,4 +672,23 @@ public void Decode_Issue2666()
672672
string path = Path.GetFullPath(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Png.Issue2666));
673673
using Image image = Image.Load(path);
674674
}
675+
676+
[Theory]
677+
678+
[InlineData(TestImages.Png.Bad.BadZTXT)]
679+
[InlineData(TestImages.Png.Bad.BadZTXT2)]
680+
public void Decode_BadZTXT(string file)
681+
{
682+
string path = Path.GetFullPath(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, file));
683+
using Image image = Image.Load(path);
684+
}
685+
686+
[Theory]
687+
[InlineData(TestImages.Png.Bad.BadZTXT)]
688+
[InlineData(TestImages.Png.Bad.BadZTXT2)]
689+
public void Info_BadZTXT(string file)
690+
{
691+
string path = Path.GetFullPath(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, file));
692+
_ = Image.Identify(path);
693+
}
675694
}

tests/ImageSharp.Tests/TestImages.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,10 @@ public static class Bad
186186
// Invalid color type.
187187
public const string ColorTypeOne = "Png/xc1n0g08.png";
188188
public const string ColorTypeNine = "Png/xc9n2c08.png";
189-
190189
public const string FlagOfGermany0000016446 = "Png/issues/flag_of_germany-0000016446.png";
190+
191+
public const string BadZTXT = "Png/issues/bad-ztxt.png";
192+
public const string BadZTXT2 = "Png/issues/bad-ztxt2.png";
191193
}
192194
}
193195

Loading
Loading

0 commit comments

Comments
 (0)