diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs index ccdf9b0b7..26604717f 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs @@ -53,6 +53,12 @@ internal sealed class ModifiedHuffmanTiffCompression : TiffBaseDecompressor if (bitReader.RunLength > 0) { + // A decoded run is untrusted and must fit the current row before the unchecked bit writer is used. + if (bitReader.RunLength > (nuint)this.Width - pixelsWritten) + { + TiffThrowHelper.ThrowImageFormatException("CCITT compression parsing error: decoded more pixels than the image width."); + } + if (bitReader.IsWhiteRun) { BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.whiteValue); @@ -86,11 +92,6 @@ internal sealed class ModifiedHuffmanTiffCompression : TiffBaseDecompressor bitReader.StartNewRow(); } - - if (pixelsWritten > (ulong)this.Width) - { - TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, decoded more pixels then image width"); - } } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs index bfc4f9bee..982f4db8b 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs @@ -319,7 +319,7 @@ internal class T4BitReader { if (this.CurValueBitsRead > this.maxCodeLength) { - TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: invalid code length read"); + TiffThrowHelper.ThrowImageFormatException("CCITT compression parsing error: invalid code length read."); } bool isMakeupCode = this.IsMakeupCode(); @@ -401,7 +401,7 @@ internal class T4BitReader if (!this.IsEndOfScanLine) { - TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: expected start of data marker not found"); + TiffThrowHelper.ThrowImageFormatException("CCITT compression parsing error: expected start of data marker not found."); } this.Reset(); diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs index d9e49aa75..02ed95256 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs @@ -72,7 +72,7 @@ internal sealed class T4TiffCompression : TiffBaseDecompressor if (bitReader.RunLength > 0) { - this.WritePixelRun(buffer, bitReader, bitsWritten); + this.WritePixelRun(buffer, bitReader, bitsWritten, pixelsWritten); bitsWritten += (int)bitReader.RunLength; pixelsWritten += bitReader.RunLength; @@ -102,12 +102,18 @@ internal sealed class T4TiffCompression : TiffBaseDecompressor if (pixelsWritten > 0 && pixelsWritten < (ulong)this.width) { bitReader.ReadNextRun(); - this.WritePixelRun(buffer, bitReader, bitsWritten); + this.WritePixelRun(buffer, bitReader, bitsWritten, pixelsWritten); } } - private void WritePixelRun(Span buffer, T4BitReader bitReader, nint bitsWritten) + private void WritePixelRun(Span buffer, T4BitReader bitReader, nint bitsWritten, nuint pixelsWritten) { + // A decoded run is untrusted and must fit the current row before the unchecked bit writer is used. + if (bitReader.RunLength > (nuint)this.width - pixelsWritten) + { + TiffThrowHelper.ThrowImageFormatException("CCITT compression parsing error: decoded more pixels than the image width."); + } + if (bitReader.IsWhiteRun) { BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.whiteValue); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/CcittTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/CcittTiffCompressionTests.cs new file mode 100644 index 000000000..ce8c770fa --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/CcittTiffCompressionTests.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.IO.Compression; +using SixLabors.ImageSharp.Formats; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression; + +[Trait("Format", "Tiff")] +public class CcittTiffCompressionTests +{ + private const string T4PocGzipBase64 = + "H4sICBOogmoCA3BvYy10aWZmLXQ0LnRpZgDty7ENwjAQhlFfjKKUoYGSPlPQZopskGVYiJFgA2zpFGUG9Kz3S19xXtelTKX0xaVEq2dbZPcNUY+u2bVtzO7vmvd72+3095792vJyfsQH" + + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgH33fP9Zj5xoBYAEA"; + + private const string ModifiedHuffmanPocGzipBase64 = + "H4sICBSogmoCA3BvYy10aWZmLW1oLnRpZgDty7ENgzAQhlEfRBElNKRMnylomSIbZBkWYiTYAFs6RZkhetb7pa84r+urDKW0xa1EraUustu66L/dZ3d19+z2prz/1M0/fx/Z2zsvx2cc" + + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/+jcL3Buw0IBYAEA"; + + [Theory] + [InlineData(T4PocGzipBase64)] + [InlineData(ModifiedHuffmanPocGzipBase64)] + public void Decode_CcittStripWithOversizedRun_ThrowsImageFormatException(string gzipBase64) + { + // Keep the reporter's exact TIFF payload compressed so the regression source remains small. + byte[] compressed = Convert.FromBase64String(gzipBase64); + using MemoryStream compressedStream = new(compressed); + using GZipStream gzipStream = new(compressedStream, CompressionMode.Decompress); + using MemoryStream tiffStream = new(); + + gzipStream.CopyTo(tiffStream); + tiffStream.Position = 0; + + ImageFormatException exception = Assert.Throws(() => Image.Load(tiffStream)); + + Assert.Equal("CCITT compression parsing error: decoded more pixels than the image width.", exception.Message); + } +}