diff --git a/Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif b/Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif new file mode 100644 index 000000000..01dca594f Binary files /dev/null and b/Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif differ diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index a32e6a005..4f3c8e390 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -4,7 +4,7 @@ from io import BytesIO import pytest -from PIL import Image, ImageFile, TiffImagePlugin +from PIL import Image, ImageFile, TiffImagePlugin, UnidentifiedImageError from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION from .helper import ( @@ -858,6 +858,19 @@ class TestFileTiff: im.load() ImageFile.LOAD_TRUNCATED_IMAGES = False + @pytest.mark.parametrize( + "test_file", + [ + "Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif", + ], + ) + @pytest.mark.timeout(2) + def test_oom(self, test_file): + with pytest.raises(UnidentifiedImageError): + with pytest.warns(UserWarning): + with Image.open(test_file): + pass + @pytest.mark.skipif(not is_win32(), reason="Windows only") class TestFileTiffW32: diff --git a/docs/releasenotes/9.3.0.rst b/docs/releasenotes/9.3.0.rst index e5a68ed9e..410666fc0 100644 --- a/docs/releasenotes/9.3.0.rst +++ b/docs/releasenotes/9.3.0.rst @@ -49,6 +49,15 @@ decode the data in its natural CMYK mode, then convert it to RGB and rearrange the channels afterwards. Trying to load the data in an incorrect mode could result in a segmentation fault. This issue was introduced in Pillow 9.1.0. +Limit SAMPLESPERPIXEL to avoid runtime DOS +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A large value in the ``SAMPLESPERPIXEL`` tag could lead to a memory and runtime DOS in +``TiffImagePlugin.py`` when setting up the context for image decoding. +This was introduced in Pillow 9.2.0, found with `OSS-Fuzz`_ and fixed by limiting +``SAMPLESPERPIXEL`` to the number of planes that we can decode. + + Other Changes ============= @@ -88,3 +97,5 @@ Show all frames with ImageShow When calling :py:meth:`~PIL.Image.Image.show` or using :py:mod:`~PIL.ImageShow`, all frames will now be shown. + +.. _OSS-Fuzz: https://github.com/google/oss-fuzz diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 04a63bd2b..1dfd5275f 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -257,6 +257,8 @@ OPEN_INFO = { (MM, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"), } +MAX_SAMPLESPERPIXEL = max(len(key_tp[4]) for key_tp in OPEN_INFO.keys()) + PREFIXES = [ b"MM\x00\x2A", # Valid TIFF header with big-endian byte order b"II\x2A\x00", # Valid TIFF header with little-endian byte order @@ -1396,6 +1398,14 @@ class TiffImageFile(ImageFile.ImageFile): SAMPLESPERPIXEL, 3 if self._compression == "tiff_jpeg" and photo in (2, 6) else 1, ) + + if samples_per_pixel > MAX_SAMPLESPERPIXEL: + # DOS check, samples_per_pixel can be a Long, and we extend the tuple below + logger.error( + "More samples per pixel than can be decoded: %s", samples_per_pixel + ) + raise SyntaxError("Invalid value for samples per pixel") + if samples_per_pixel < bps_actual_count: # If a file has more values in bps_tuple than expected, # remove the excess.