Browse Source

Tiff decoding robustness improvements (#2550)

* Faster Handling of circular offsets

* Handle early EOF during ZLib inflate

* more checks

---------

Co-authored-by: antonfirsov <antonfir@gmail.com>
pull/2552/head
James Jackson-South 3 years ago
committed by GitHub
parent
commit
b8e8b8613d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 45
      src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs
  2. 22
      src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs
  3. 28
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  4. 2
      tests/ImageSharp.Tests/TestImages.cs
  5. 3
      tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_TiledWithBadZlib_tiled-0000023664.png
  6. 3
      tests/Images/Input/Tiff/Issues/JpegCompressedGray-0000539558.tiff
  7. 3
      tests/Images/Input/Tiff/Issues/tiled-0000023664.tiff

45
src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs

@ -161,6 +161,11 @@ internal sealed class ZlibInflateStream : Stream
bytesToRead = Math.Min(count - totalBytesRead, this.currentDataRemaining);
this.currentDataRemaining -= bytesToRead;
bytesRead = this.innerStream.Read(buffer, offset, bytesToRead);
if (bytesRead == 0)
{
return totalBytesRead;
}
totalBytesRead += bytesRead;
}
@ -168,22 +173,13 @@ internal sealed class ZlibInflateStream : Stream
}
/// <inheritdoc/>
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
/// <inheritdoc/>
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void SetLength(long value) => throw new NotSupportedException();
/// <inheritdoc/>
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
/// <inheritdoc/>
protected override void Dispose(bool disposing)
@ -246,22 +242,17 @@ internal sealed class ZlibInflateStream : Stream
// 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;
}
return false;
}
}
else if (isCriticalChunk)
{
throw new ImageFormatException($"Bad method for ZLIB header: cmf={cmf}");
}
else
{
if (isCriticalChunk)
{
throw new ImageFormatException($"Bad method for ZLIB header: cmf={cmf}");
}
else
{
return false;
}
return false;
}
// The preset dictionary.
@ -270,7 +261,11 @@ internal sealed class ZlibInflateStream : Stream
{
// We don't need this for inflate so simply skip by the next four bytes.
// https://tools.ietf.org/html/rfc1950#page-6
this.innerStream.Read(ChecksumBuffer, 0, 4);
if (this.innerStream.Read(ChecksumBuffer, 0, 4) != 4)
{
return false;
}
this.currentDataRemaining -= 4;
}

22
src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs

@ -40,7 +40,7 @@ internal class DirectoryReader
public IList<ExifProfile> Read()
{
this.ByteOrder = ReadByteOrder(this.stream);
var headerReader = new HeaderReader(this.stream, this.ByteOrder);
HeaderReader headerReader = new(this.stream, this.ByteOrder);
headerReader.ReadFileHeader();
this.nextIfdOffset = headerReader.FirstIfdOffset;
@ -52,7 +52,12 @@ internal class DirectoryReader
private static ByteOrder ReadByteOrder(Stream stream)
{
Span<byte> headerBytes = stackalloc byte[2];
stream.Read(headerBytes);
if (stream.Read(headerBytes) != 2)
{
throw TiffThrowHelper.ThrowInvalidHeader();
}
if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian)
{
return ByteOrder.LittleEndian;
@ -68,10 +73,10 @@ internal class DirectoryReader
private IList<ExifProfile> ReadIfds(bool isBigTiff)
{
var readers = new List<EntryReader>();
List<EntryReader> readers = new();
while (this.nextIfdOffset != 0 && this.nextIfdOffset < (ulong)this.stream.Length)
{
var reader = new EntryReader(this.stream, this.ByteOrder, this.allocator);
EntryReader reader = new(this.stream, this.ByteOrder, this.allocator);
reader.ReadTags(isBigTiff, this.nextIfdOffset);
if (reader.BigValues.Count > 0)
@ -85,6 +90,11 @@ internal class DirectoryReader
}
}
if (this.nextIfdOffset >= reader.NextIfdOffset && reader.NextIfdOffset != 0)
{
TiffThrowHelper.ThrowImageFormatException("TIFF image contains circular directory offsets");
}
this.nextIfdOffset = reader.NextIfdOffset;
readers.Add(reader);
@ -94,11 +104,11 @@ internal class DirectoryReader
}
}
var list = new List<ExifProfile>(readers.Count);
List<ExifProfile> list = new(readers.Count);
foreach (EntryReader reader in readers)
{
reader.ReadBigValues();
var profile = new ExifProfile(reader.Values, reader.InvalidTags);
ExifProfile profile = new(reader.Values, reader.InvalidTags);
list.Add(profile);
}

28
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs

@ -2,7 +2,6 @@
// Licensed under the Six Labors Split License.
// ReSharper disable InconsistentNaming
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Tiff;
@ -666,6 +665,33 @@ public class TiffDecoderTests : TiffDecoderBaseTester
public void TiffDecoder_CanDecode_TiledWithNonEqualWidthAndHeight<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(JpegCompressedGray0000539558, PixelTypes.Rgba32)]
public void TiffDecoder_ThrowsException_WithCircular_IFD_Offsets<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
=> Assert.Throws<ImageFormatException>(
() =>
{
using (provider.GetImage(TiffDecoder.Instance))
{
}
});
[Theory]
[WithFile(Tiled0000023664, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_TiledWithBadZlib<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(TiffDecoder.Instance);
// ImageMagick cannot decode this image.
image.DebugSave(provider);
image.CompareToReferenceOutput(
ImageComparer.Exact,
provider,
appendPixelTypeToFileName: false);
}
[Theory]
[WithFileCollection(nameof(MultiframeTestImages), PixelTypes.Rgba32)]
public void DecodeMultiframe<TPixel>(TestImageProvider<TPixel> provider)

2
tests/ImageSharp.Tests/TestImages.cs

@ -980,6 +980,8 @@ public static class TestImages
public const string Issues2149 = "Tiff/Issues/Group4CompressionWithStrips.tiff";
public const string Issues2255 = "Tiff/Issues/Issue2255.png";
public const string Issues2435 = "Tiff/Issues/Issue2435.tiff";
public const string JpegCompressedGray0000539558 = "Tiff/Issues/JpegCompressedGray-0000539558.tiff";
public const string Tiled0000023664 = "Tiff/Issues/tiled-0000023664.tiff";
public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff";
public const string SmallRgbLzw = "Tiff/rgb_small_lzw.tiff";

3
tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_TiledWithBadZlib_tiled-0000023664.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:456f0699fbba95953fbdba0164168583cc7d2efe1f858a6570938e8797b398cd
size 15586

3
tests/Images/Input/Tiff/Issues/JpegCompressedGray-0000539558.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1f1ca630b5e46c7b5f21100fa8c0fbf27b79ca9da8cd95897667b64aedccf6e5
size 539558

3
tests/Images/Input/Tiff/Issues/tiled-0000023664.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:eb28a028b2467b9b42451d9cb30d8170fd91ff4c4046b69cc1ae7f123bf7ba6f
size 23664
Loading…
Cancel
Save