Skip to content

Commit f758fad

Browse files
Merge pull request #3003 from SixLabors/js/fix-2959
V4: Prevent negative allocation attempt for huge TIFF files
2 parents 44f5af1 + 5a9f42d commit f758fad

File tree

2 files changed

+93
-18
lines changed

2 files changed

+93
-18
lines changed

src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,20 @@ protected TiffBaseDecompressor(MemoryAllocator memoryAllocator, int width, int b
2828
/// Decompresses image data into the supplied buffer.
2929
/// </summary>
3030
/// <param name="stream">The <see cref="Stream" /> to read image data from.</param>
31-
/// <param name="stripOffset">The strip offset of stream.</param>
32-
/// <param name="stripByteCount">The number of bytes to read from the input stream.</param>
31+
/// <param name="offset">The data offset within the stream.</param>
32+
/// <param name="count">The number of bytes to read from the input stream.</param>
3333
/// <param name="stripHeight">The height of the strip.</param>
3434
/// <param name="buffer">The output buffer for uncompressed data.</param>
3535
/// <param name="cancellationToken">The token to monitor cancellation.</param>
36-
public void Decompress(BufferedReadStream stream, ulong stripOffset, ulong stripByteCount, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken)
36+
public void Decompress(BufferedReadStream stream, ulong offset, ulong count, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken)
3737
{
38-
DebugGuard.MustBeLessThanOrEqualTo(stripOffset, (ulong)long.MaxValue, nameof(stripOffset));
39-
DebugGuard.MustBeLessThanOrEqualTo(stripByteCount, (ulong)long.MaxValue, nameof(stripByteCount));
38+
DebugGuard.MustBeLessThanOrEqualTo(offset, (ulong)long.MaxValue, nameof(offset));
39+
DebugGuard.MustBeLessThanOrEqualTo(count, (ulong)int.MaxValue, nameof(count));
4040

41-
stream.Seek((long)stripOffset, SeekOrigin.Begin);
42-
this.Decompress(stream, (int)stripByteCount, stripHeight, buffer, cancellationToken);
41+
stream.Seek((long)offset, SeekOrigin.Begin);
42+
this.Decompress(stream, (int)count, stripHeight, buffer, cancellationToken);
4343

44-
if ((long)stripOffset + (long)stripByteCount < stream.Position)
44+
if ((long)offset + (long)count < stream.Position)
4545
{
4646
TiffThrowHelper.ThrowImageFormatException("Out of range when reading a strip.");
4747
}

src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

Lines changed: 85 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Buffers;
66
using System.Runtime.CompilerServices;
77
using SixLabors.ImageSharp.Formats.Tiff.Compression;
8+
using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors;
89
using SixLabors.ImageSharp.Formats.Tiff.Constants;
910
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
1011
using SixLabors.ImageSharp.IO;
@@ -441,8 +442,14 @@ private void DecodeStripsPlanar<TPixel>(
441442
{
442443
for (int stripIndex = 0; stripIndex < stripBuffers.Length; stripIndex++)
443444
{
444-
int uncompressedStripSize = this.CalculateStripBufferSize(width, rowsPerStrip, stripIndex);
445-
stripBuffers[stripIndex] = this.memoryAllocator.Allocate<byte>(uncompressedStripSize);
445+
ulong uncompressedStripSize = this.CalculateStripBufferSize(width, rowsPerStrip, stripIndex);
446+
447+
if (uncompressedStripSize > int.MaxValue)
448+
{
449+
TiffThrowHelper.ThrowNotSupported("Strips larger than Int32.MaxValue bytes are not supported for compressed images.");
450+
}
451+
452+
stripBuffers[stripIndex] = this.memoryAllocator.Allocate<byte>((int)uncompressedStripSize);
446453
}
447454

448455
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(width, bitsPerPixel, frame.Metadata);
@@ -507,15 +514,83 @@ private void DecodeStripsChunky<TPixel>(
507514
rowsPerStrip = height;
508515
}
509516

510-
int uncompressedStripSize = this.CalculateStripBufferSize(width, rowsPerStrip);
517+
ulong uncompressedStripSize = this.CalculateStripBufferSize(width, rowsPerStrip);
511518
int bitsPerPixel = this.BitsPerPixel;
512519

513-
using IMemoryOwner<byte> stripBuffer = this.memoryAllocator.Allocate<byte>(uncompressedStripSize, AllocationOptions.Clean);
514-
Span<byte> stripBufferSpan = stripBuffer.GetSpan();
515-
Buffer2D<TPixel> pixels = frame.PixelBuffer;
516-
517520
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(width, bitsPerPixel, frame.Metadata);
518521
TiffBaseColorDecoder<TPixel> colorDecoder = this.CreateChunkyColorDecoder<TPixel>();
522+
Buffer2D<TPixel> pixels = frame.PixelBuffer;
523+
524+
// There exists in this world TIFF files with uncompressed strips larger than Int32.MaxValue.
525+
// We can read them, but we cannot allocate a buffer that large to hold the uncompressed data.
526+
// In this scenario we fall back to reading and decoding one row at a time.
527+
//
528+
// The NoneTiffCompression decompressor can be used to read individual rows since we have
529+
// a guarantee that each row required the same number of bytes.
530+
if (decompressor is NoneTiffCompression none && uncompressedStripSize > int.MaxValue)
531+
{
532+
ulong bytesPerRowU = this.CalculateStripBufferSize(width, 1);
533+
534+
// This should never happen, but we check just to be sure.
535+
if (bytesPerRowU > int.MaxValue)
536+
{
537+
TiffThrowHelper.ThrowNotSupported("Strips larger than Int32.MaxValue bytes are not supported for compressed images.");
538+
}
539+
540+
int bytesPerRow = (int)bytesPerRowU;
541+
using IMemoryOwner<byte> rowBufferOwner = this.memoryAllocator.Allocate<byte>(bytesPerRow, AllocationOptions.Clean);
542+
Span<byte> rowBuffer = rowBufferOwner.GetSpan();
543+
for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++)
544+
{
545+
cancellationToken.ThrowIfCancellationRequested();
546+
547+
int stripHeight = stripIndex < stripOffsets.Length - 1 || height % rowsPerStrip == 0
548+
? rowsPerStrip
549+
: height % rowsPerStrip;
550+
551+
int top = rowsPerStrip * stripIndex;
552+
if (top + stripHeight > height)
553+
{
554+
break;
555+
}
556+
557+
ulong baseOffset = stripOffsets[stripIndex];
558+
ulong available = stripByteCounts[stripIndex];
559+
ulong required = (ulong)bytesPerRow * (ulong)stripHeight;
560+
if (available < required)
561+
{
562+
break;
563+
}
564+
565+
for (int r = 0; r < stripHeight; r++)
566+
{
567+
cancellationToken.ThrowIfCancellationRequested();
568+
569+
ulong rowOffset = baseOffset + ((ulong)r * (ulong)bytesPerRow);
570+
571+
// Use the NoneTiffCompression decompressor to read exactly one row.
572+
none.Decompress(
573+
this.inputStream,
574+
rowOffset,
575+
(ulong)bytesPerRow,
576+
1,
577+
rowBuffer,
578+
cancellationToken);
579+
580+
colorDecoder.Decode(rowBuffer, pixels, 0, top + r, width, 1);
581+
}
582+
}
583+
584+
return;
585+
}
586+
587+
if (uncompressedStripSize > int.MaxValue)
588+
{
589+
TiffThrowHelper.ThrowNotSupported("Strips larger than Int32.MaxValue bytes are not supported for compressed images.");
590+
}
591+
592+
using IMemoryOwner<byte> stripBuffer = this.memoryAllocator.Allocate<byte>((int)uncompressedStripSize, AllocationOptions.Clean);
593+
Span<byte> stripBufferSpan = stripBuffer.GetSpan();
519594

520595
for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++)
521596
{
@@ -808,7 +883,7 @@ private IMemoryOwner<ulong> ConvertNumbers(Array array, out Span<ulong> span)
808883
/// <param name="height">The height for the desired pixel buffer.</param>
809884
/// <param name="plane">The index of the plane for planar image configuration (or zero for chunky).</param>
810885
/// <returns>The size (in bytes) of the required pixel buffer.</returns>
811-
private int CalculateStripBufferSize(int width, int height, int plane = -1)
886+
private ulong CalculateStripBufferSize(int width, int height, int plane = -1)
812887
{
813888
DebugGuard.MustBeLessThanOrEqualTo(plane, 3, nameof(plane));
814889

@@ -841,8 +916,8 @@ private int CalculateStripBufferSize(int width, int height, int plane = -1)
841916
}
842917
}
843918

844-
int bytesPerRow = ((width * bitsPerPixel) + 7) / 8;
845-
return bytesPerRow * height;
919+
ulong bytesPerRow = (((ulong)width * (ulong)bitsPerPixel) + 7) / 8;
920+
return bytesPerRow * (ulong)height;
846921
}
847922

848923
[MethodImpl(MethodImplOptions.AggressiveInlining)]

0 commit comments

Comments
 (0)