Skip to content

Decode TIFF files with large strips and backwards directory chains - #3231

Open
jafin wants to merge 1 commit into
SixLabors:mainfrom
jafin:jf/tiff-decoder-malformed-input
Open

jafin wants to merge 1 commit into
SixLabors:mainfrom
jafin:jf/tiff-decoder-malformed-input

Conversation

@jafin

@jafin jafin commented Oct 17, 2026 •

Copy link
Copy Markdown

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

Description

This PR fixes several cases where the TIFF decoder rejects valid files, or fails on large files with a misleading message.

RowsPerStrip missing or above the image height

  • Problem: a missing RowsPerStrip was stored as int.MaxValue. The chunky path replaced it with the image height, but the planar path did not. A planar file without the entry failed with "Strips larger than Int32.MaxValue bytes are not supported for compressed images". A legal RowsPerStrip larger than the height (for example 100000 on a 4000x2 RGB image) made the decoder ask for a 1.2 GB strip buffer, which is over the default single buffer limit.
  • Change: DecodeImageWithStrips now limits RowsPerStrip to the image height. A missing entry means the height. Both layouts then size their strip buffers from rows that exist.

Uncompressed strips larger than the single buffer limit

  • Problem: an uncompressed chunky strip was read one row at a time only when it was larger than Int32.MaxValue. A strip between the allocator's single buffer limit (1 GB by default) and 2 GB failed, even when the decoded image fit in memory. Planar strips had no row fallback at all.
  • Change: the chunky row fallback now starts at SingleBufferAllocationLimitBytes. The planar path gets the same fallback: it reads each plane's row at its offset in the strip and never reads past the strip's byte count. Subsampled YCbCr data stores chroma for a block of rows together, so its rows cannot be decoded one at a time. It is excluded from the fallback and still decodes the whole strip.

Allocation failures report "0x0"

  • Problem: Dimensions was set only after every frame had decoded. When an allocation failed, the message read "possibly degenerate dimensions: 0x0". Also, a frame that failed to decode was never disposed, so its pixel buffer leaked.
  • Change: the root frame's size is recorded before the frame is allocated. A frame that throws while it decodes is now disposed.

Directories stored out of file order

  • Problem: the directory reader treated any next-IFD offset lower than the current one as a loop. TIFF allows IFDs at any offset, so a valid file whose second IFD comes before the first in the file failed with "TIFF image contains circular directory offsets".
  • Change: the reader keeps a set of the offsets it has already read. It reports a loop only when the chain comes back to one of them. The directory count limit stays as it was.

Testing

New TiffDecoderStripLayoutTests build small TIFF files in memory (no new image files) and cover:

  • chunky and planar files with no RowsPerStrip, or with RowsPerStrip above the height, checked pixel by pixel;
  • chunky and planar strips above a 1 MB allocation limit, with one strip and with several strips, compared with a decode that has no limit;
  • the real dimensions in the error message when the frame does not fit;
  • IFDs in reverse file order;
  • loops of one, two and three IFDs, in ascending and reverse order, which still fail fast.

All 828 tests in the Tiff area pass in Release on net10.0.

Fixes #3243

A missing RowsPerStrip, or one above the image height, is now limited to the height, so planar files without
the entry decode and the strip buffer only holds rows that exist. Uncompressed strips, chunky or planar, that are
larger than the single buffer allocation limit are read one row at a time. The image dimensions are recorded
before the root frame is allocated, so an allocation failure reports the real size, and a frame that fails to
decode is released. Directory chains that go backwards in the file are accepted; only a revisited directory is
reported as circular.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TIFF decoder rejects valid files: missing or large RowsPerStrip, strips above the buffer limit, backwards IFD chains, and "0x0" in allocation errors

1 participant