Browse Source

Fix tiled CCITT decompressor width handling

Use tile width (instead of full image width) when creating T4/T6/Modified Huffman decompressors for tiled TIFF decoding, preventing row-size mismatches and false overrun failures. Also standardize the CCITT overrun error message and add regression coverage for both strip-based and tiled reporter samples that must throw when decoded pixels exceed image width.
pull/3176/head
James Jackson-South 2 weeks ago
parent
commit
9ee7d1dd5b
  1. 4
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs
  2. 10
      src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
  3. 2
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  4. 31
      tests/ImageSharp.Tests/Formats/Tiff/Compression/CcittTiffCompressionTests.cs

4
src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs

@ -163,7 +163,7 @@ internal sealed class T6TiffCompression : TiffBaseDecompressor
int runLength = (int)bitReader.RunLength;
if (runLength > (uint)(scanline.Length - unpacked))
{
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error");
TiffThrowHelper.ThrowImageFormatException("CCITT compression parsing error: decoded more pixels than the image width.");
}
scanline.Slice(unpacked, runLength).Fill(fillByte);
@ -175,7 +175,7 @@ internal sealed class T6TiffCompression : TiffBaseDecompressor
runLength = (int)bitReader.RunLength;
if (runLength > (uint)(scanline.Length - unpacked))
{
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error");
TiffThrowHelper.ThrowImageFormatException("CCITT compression parsing error: decoded more pixels than the image width.");
}
scanline.Slice(unpacked, runLength).Fill(fillByte);

10
src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs

@ -31,6 +31,10 @@ internal static class TiffDecompressorsFactory
int tileWidth = 0,
int tileHeight = 0)
{
// Fax compression emits one decoded row at a time, so tiled images must use
// the tile row width that was used to size the caller's destination buffer.
int faxWidth = isTiled ? tileWidth : width;
switch (method)
{
case TiffDecoderCompressionType.None:
@ -53,15 +57,15 @@ internal static class TiffDecompressorsFactory
case TiffDecoderCompressionType.T4:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new T4TiffCompression(allocator, fillOrder, width, bitsPerPixel, faxOptions, photometricInterpretation);
return new T4TiffCompression(allocator, fillOrder, faxWidth, bitsPerPixel, faxOptions, photometricInterpretation);
case TiffDecoderCompressionType.T6:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new T6TiffCompression(allocator, fillOrder, width, bitsPerPixel, photometricInterpretation);
return new T6TiffCompression(allocator, fillOrder, faxWidth, bitsPerPixel, photometricInterpretation);
case TiffDecoderCompressionType.HuffmanRle:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new ModifiedHuffmanTiffCompression(allocator, fillOrder, width, bitsPerPixel, photometricInterpretation);
return new ModifiedHuffmanTiffCompression(allocator, fillOrder, faxWidth, bitsPerPixel, photometricInterpretation);
case TiffDecoderCompressionType.Jpeg:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");

2
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -693,7 +693,7 @@ internal class TiffDecoderCore : ImageDecoderCore
tilesBuffers[i] = this.memoryAllocator.Allocate<byte>(uncompressedTilesSize, AllocationOptions.Clean);
}
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(frame.Width, bitsPerPixel, frame.Metadata);
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(frame.Width, bitsPerPixel, frame.Metadata, true, tileWidth, tileLength);
TiffBasePlanarColorDecoder<TPixel> colorDecoder = this.CreatePlanarColorDecoder<TPixel>(frame.Metadata);
int tileIndex = 0;

31
tests/ImageSharp.Tests/Formats/Tiff/Compression/CcittTiffCompressionTests.cs

@ -17,22 +17,43 @@ public class CcittTiffCompressionTests
"H4sICBSogmoCA3BvYy10aWZmLW1oLnRpZgDty7ENgzAQhlEfRBElNKRMnylomSIbZBkWYiTYAFs6RZkhetb7pa84r+urDKW0xa1EraUustu66L/dZ3d19+z2prz/1M0/fx/Z2zsvx2cc" +
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/+jcL3Buw0IBYAEA";
private const string TiledT4ReporterPocGzipBase64 =
"H4sIAAAAAAACCu3UKw7CUBCG0RkuIeC6AxQGj8TwEHWshyWxOhI2QC8Z0QWAIDnNmeQXran4xnEf64jYROQyMvo8RtYepltk++x+rXabblW7P6fZ++fZvtS+T3et/djVR8M2n/Btr1v6C/zCQbQQLUQLRAvRQrRAtBAtRAtEC9FCtEC0EC3+Mlpv/6RrkiQmAAA=";
[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.
using MemoryStream tiffStream = CreateTiffStream(gzipBase64);
ImageFormatException exception = Assert.Throws<ImageFormatException>(() => Image.Load(tiffStream));
Assert.Equal("CCITT compression parsing error: decoded more pixels than the image width.", exception.Message);
}
[Fact]
public void Decode_TiledCcittReporterSample_ThrowsImageFormatException()
{
// This is the reporter's exact tiled T4 TIFF sample, gzip-compressed only to keep the test source small.
using MemoryStream tiffStream = CreateTiffStream(TiledT4ReporterPocGzipBase64);
ImageFormatException exception = Assert.Throws<ImageFormatException>(() => Image.Load(tiffStream));
Assert.Equal("CCITT compression parsing error: decoded more pixels than the image width.", exception.Message);
}
private static MemoryStream CreateTiffStream(string gzipBase64)
{
// Keep the TIFF payloads 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();
MemoryStream tiffStream = new();
gzipStream.CopyTo(tiffStream);
tiffStream.Position = 0;
ImageFormatException exception = Assert.Throws<ImageFormatException>(() => Image.Load(tiffStream));
Assert.Equal("CCITT compression parsing error: decoded more pixels than the image width.", exception.Message);
return tiffStream;
}
}

Loading…
Cancel
Save