Skip to content

Commit 3e042a2

Browse files
Merge branch 'release/3.1.x' into js/v3-updates
2 parents f44b761 + 3f22857 commit 3e042a2

File tree

7 files changed

+52
-21
lines changed

7 files changed

+52
-21
lines changed

.github/workflows/build-and-test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44
push:
55
branches:
66
- main
7+
- release/*
78
tags:
89
- "v*"
910
pull_request:

src/ImageSharp/Formats/Png/PngDecoderCore.cs

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
125125
/// A reusable Crc32 hashing instance.
126126
/// </summary>
127127
private readonly Crc32 crc32 = new();
128+
129+
/// The maximum memory in bytes that a zTXt, sPLT, iTXt, iCCP, or unknown chunk can occupy when decompressed.
130+
/// </summary>
131+
private readonly int maxUncompressedLength;
128132

129133
/// <summary>
130134
/// Initializes a new instance of the <see cref="PngDecoderCore"/> class.
@@ -138,6 +142,7 @@ public PngDecoderCore(PngDecoderOptions options)
138142
this.skipMetadata = options.GeneralOptions.SkipMetadata;
139143
this.memoryAllocator = this.configuration.MemoryAllocator;
140144
this.pngCrcChunkHandling = options.PngCrcChunkHandling;
145+
this.maxUncompressedLength = options.MaxUncompressedAncillaryChunkSizeBytes;
141146
}
142147

143148
internal PngDecoderCore(PngDecoderOptions options, bool colorMetadataOnly)
@@ -149,6 +154,7 @@ internal PngDecoderCore(PngDecoderOptions options, bool colorMetadataOnly)
149154
this.configuration = options.GeneralOptions.Configuration;
150155
this.memoryAllocator = this.configuration.MemoryAllocator;
151156
this.pngCrcChunkHandling = options.PngCrcChunkHandling;
157+
this.maxUncompressedLength = options.MaxUncompressedAncillaryChunkSizeBytes;
152158
}
153159

154160
/// <inheritdoc/>
@@ -602,23 +608,7 @@ private static void ReadGammaChunk(PngMetadata pngMetadata, ReadOnlySpan<byte> d
602608
private void InitializeImage<TPixel>(ImageMetadata metadata, FrameControl frameControl, out Image<TPixel> image)
603609
where TPixel : unmanaged, IPixel<TPixel>
604610
{
605-
// When ignoring data CRCs, we can't use the image constructor that leaves the buffer uncleared.
606-
if (this.pngCrcChunkHandling is PngCrcChunkHandling.IgnoreData or PngCrcChunkHandling.IgnoreAll)
607-
{
608-
image = new Image<TPixel>(
609-
this.configuration,
610-
this.header.Width,
611-
this.header.Height,
612-
metadata);
613-
}
614-
else
615-
{
616-
image = Image.CreateUninitialized<TPixel>(
617-
this.configuration,
618-
this.header.Width,
619-
this.header.Height,
620-
metadata);
621-
}
611+
image = new Image<TPixel>(this.configuration, this.header.Width, this.header.Height, metadata);
622612

623613
PngFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetPngMetadata();
624614
frameMetadata.FromChunk(in frameControl);
@@ -1575,7 +1565,7 @@ private void ReadColorProfileChunk(ImageMetadata metadata, ReadOnlySpan<byte> da
15751565

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

1578-
if (this.TryDecompressZlibData(compressedData, out byte[] iccpProfileBytes))
1568+
if (this.TryDecompressZlibData(compressedData, this.maxUncompressedLength, out byte[] iccpProfileBytes))
15791569
{
15801570
metadata.IccProfile = new IccProfile(iccpProfileBytes);
15811571
}
@@ -1585,9 +1575,10 @@ private void ReadColorProfileChunk(ImageMetadata metadata, ReadOnlySpan<byte> da
15851575
/// Tries to decompress zlib compressed data.
15861576
/// </summary>
15871577
/// <param name="compressedData">The compressed data.</param>
1578+
/// <param name="maxLength">The maximum uncompressed length.</param>
15881579
/// <param name="uncompressedBytesArray">The uncompressed bytes array.</param>
15891580
/// <returns>True, if de-compressing was successful.</returns>
1590-
private unsafe bool TryDecompressZlibData(ReadOnlySpan<byte> compressedData, out byte[] uncompressedBytesArray)
1581+
private unsafe bool TryDecompressZlibData(ReadOnlySpan<byte> compressedData, int maxLength, out byte[] uncompressedBytesArray)
15911582
{
15921583
fixed (byte* compressedDataBase = compressedData)
15931584
{
@@ -1607,6 +1598,12 @@ private unsafe bool TryDecompressZlibData(ReadOnlySpan<byte> compressedData, out
16071598
int bytesRead = inflateStream.CompressedStream.Read(destUncompressedData, 0, destUncompressedData.Length);
16081599
while (bytesRead != 0)
16091600
{
1601+
if (memoryStreamOutput.Length > maxLength)
1602+
{
1603+
uncompressedBytesArray = Array.Empty<byte>();
1604+
return false;
1605+
}
1606+
16101607
memoryStreamOutput.Write(destUncompressedData[..bytesRead]);
16111608
bytesRead = inflateStream.CompressedStream.Read(destUncompressedData, 0, destUncompressedData.Length);
16121609
}
@@ -1749,7 +1746,7 @@ private void ReadInternationalTextChunk(ImageMetadata metadata, ReadOnlySpan<byt
17491746
/// <returns>The <see cref="bool"/>.</returns>
17501747
private bool TryDecompressTextData(ReadOnlySpan<byte> compressedData, Encoding encoding, [NotNullWhen(true)] out string? value)
17511748
{
1752-
if (this.TryDecompressZlibData(compressedData, out byte[] uncompressedData))
1749+
if (this.TryDecompressZlibData(compressedData, this.maxUncompressedLength, out byte[] uncompressedData))
17531750
{
17541751
value = encoding.GetString(uncompressedData);
17551752
return true;

src/ImageSharp/Formats/Png/PngDecoderOptions.cs

Lines changed: 6 additions & 0 deletions
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

Lines changed: 19 additions & 0 deletions
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

Lines changed: 3 additions & 1 deletion
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

Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)