Browse Source

Guard CCITT runs that exceed row width

Adds explicit bounds checks before writing decoded CCITT runs in both T4 and Modified Huffman TIFF decompressors, preventing oversized runs from writing past the current row. Error messages were also standardized/capitalized for CCITT parsing failures. Includes new regression tests using compressed TIFF PoCs to verify decoding now throws ImageFormatException with the expected width-overrun message.
pull/3176/head
James Jackson-South 2 weeks ago
parent
commit
e8892ecd8a
  1. 11
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs
  2. 4
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs
  3. 12
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs
  4. 38
      tests/ImageSharp.Tests/Formats/Tiff/Compression/CcittTiffCompressionTests.cs

11
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");
}
}
}

4
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();

12
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<byte> buffer, T4BitReader bitReader, nint bitsWritten)
private void WritePixelRun(Span<byte> 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);

38
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<ImageFormatException>(() => Image.Load(tiffStream));
Assert.Equal("CCITT compression parsing error: decoded more pixels than the image width.", exception.Message);
}
}
Loading…
Cancel
Save