From 17e624e522de2f7963b393584313c2fdbbf30d65 Mon Sep 17 00:00:00 2001 From: Frederick Price Date: Sat, 22 Apr 2023 00:20:11 -0400 Subject: [PATCH 1/4] Update documentation --- CHANGES.rst | 3 +++ docs/releasenotes/6.2.2.5.rst | 2 ++ 2 files changed, 5 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 635b7cc73a5..05ce203ddac 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -27,6 +27,9 @@ Changelog (Pillow) combination of \r and \n as line endings. [rickprice] +- Fix CVE-2022-45199: Pillow before 9.3.0 allows denial of service via SAMPLESPERPIXEL. + [rickprice] + - Fix CVE-2021-28676: FliDecode did not properly check that the block advance was non-zero, potentally leading to an infinite loop on load. [rickprice] diff --git a/docs/releasenotes/6.2.2.5.rst b/docs/releasenotes/6.2.2.5.rst index 4130c871073..a18c5c37bd5 100644 --- a/docs/releasenotes/6.2.2.5.rst +++ b/docs/releasenotes/6.2.2.5.rst @@ -37,3 +37,5 @@ This release addresses several critical CVEs. Pillow in the open phase, before an image was accepted for opening. +:cve: `CVE-2022-45199`: Pillow before 9.3.0 allows denial of service via SAMPLESPERPIXEL. + From 812b46901ef4abeea2523ccc1a667765465d55bb Mon Sep 17 00:00:00 2001 From: Frederick Price Date: Fri, 21 Apr 2023 23:47:55 -0400 Subject: [PATCH 2/4] Cherry picked 13f2c5ae14901c89c38f898496102afd9daeaf6d --- ...5817ca0f8c663be7ab4b9e717b02c661e66834.tif | Bin 0 -> 88 bytes Tests/test_file_tiff.py | 69 +++++++++--------- src/PIL/TiffImagePlugin.py | 25 ++++++- 3 files changed, 54 insertions(+), 40 deletions(-) create mode 100644 Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif diff --git a/Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif b/Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif new file mode 100644 index 0000000000000000000000000000000000000000..01dca594f53e22fda9b11ed5b704326680af1b8c GIT binary patch literal 88 zcmebD)MDUZU|`^8U|?isU<9(bfS3`=2FWl%*#bZ|Gn5Td$A-ifWn=;Cox~s{WCDf& DV5 len(bps_tuple) and len(bps_tuple) == 1: - bps_tuple = bps_tuple * bps_count + bps_actual_count = len(bps_tuple) + samples_per_pixel = self.tag_v2.get( + 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. + bps_tuple = bps_tuple[:samples_per_pixel] + elif samples_per_pixel > bps_actual_count and bps_actual_count == 1: + # If a file has only one value in bps_tuple, when it should have more, + # presume it is the same number of bits for all of the samples. + bps_tuple = bps_tuple * samples_per_pixel # mode: check photometric interpretation and bits per pixel key = ( From 5eaa850968cf9c0e0e0207babef00eb8b5fcc86a Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Fri, 28 Oct 2022 14:46:20 +0200 Subject: [PATCH 3/4] Tighter test case (cherry picked from commit 05b175ef88c22f5c416bc9b8d5b897dea1abbf2c) --- Tests/test_file_tiff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index f676165eb13..7d45419dcac 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -611,7 +611,7 @@ def test_fd_leak(self): def test_oom(self, test_file): with pytest.raises(UnidentifiedImageError): with Image.open(test_file) as im: - im.load() + pass # this is an mmaped file. with Image.open("Tests/images/uint16_1_4660.tif") as im: From 734edcada3f17ef29f3e920f47e5c50e4847555b Mon Sep 17 00:00:00 2001 From: Frederick Price Date: Sat, 22 Apr 2023 00:14:35 -0400 Subject: [PATCH 4/4] Remove warning --- Tests/test_file_tiff.py | 49 +++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 7d45419dcac..34cbb7b67ad 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -65,14 +65,16 @@ def test_wrong_bits_per_sample(self): self.assertEqual(im.mode, "RGBA") self.assertEqual(im.size, (52, 53)) - self.assertEqual(im.tile, [("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))]) + self.assertEqual( + im.tile, [("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))]) im.load() def test_set_legacy_api(self): ifd = TiffImagePlugin.ImageFileDirectory_v2() with self.assertRaises(Exception) as e: ifd.legacy_api = None - self.assertEqual(str(e.exception), "Not allowing setting of legacy api") + self.assertEqual(str(e.exception), + "Not allowing setting of legacy api") def test_size(self): filename = "Tests/images/pil168.tif" @@ -92,8 +94,10 @@ def test_xyres_tiff(self): self.assertIsInstance(im.tag[Y_RESOLUTION][0], tuple) # v2 api - self.assertIsInstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational) - self.assertIsInstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational) + self.assertIsInstance( + im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational) + self.assertIsInstance( + im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational) self.assertEqual(im.info["dpi"], (72.0, 72.0)) @@ -102,8 +106,10 @@ def test_xyres_fallback_tiff(self): im = Image.open(filename) # v2 api - self.assertIsInstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational) - self.assertIsInstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational) + self.assertIsInstance( + im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational) + self.assertIsInstance( + im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational) self.assertRaises(KeyError, lambda: im.tag_v2[RESOLUTION_UNIT]) # Legacy. @@ -158,10 +164,12 @@ def test_save_setting_missing_resolution(self): def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, TiffImagePlugin.TiffImageFile, invalid_file) + self.assertRaises( + SyntaxError, TiffImagePlugin.TiffImageFile, invalid_file) TiffImagePlugin.PREFIXES.append(b"\xff\xd8\xff\xe0") - self.assertRaises(SyntaxError, TiffImagePlugin.TiffImageFile, invalid_file) + self.assertRaises( + SyntaxError, TiffImagePlugin.TiffImageFile, invalid_file) TiffImagePlugin.PREFIXES.pop() def test_bad_exif(self): @@ -236,7 +244,8 @@ def test_32bit_float(self): im.load() self.assertEqual(im.getpixel((0, 0)), -0.4526388943195343) - self.assertEqual(im.getextrema(), (-3.140936851501465, 3.140684127807617)) + self.assertEqual( + im.getextrema(), (-3.140936851501465, 3.140684127807617)) def test_unknown_pixel_mode(self): self.assertRaises( @@ -446,7 +455,8 @@ def test_gray_semibyte_per_pixel(self): self.assert_image_equal(im, im2) def test_with_underscores(self): - kwargs = {"resolution_unit": "inch", "x_resolution": 72, "y_resolution": 36} + kwargs = {"resolution_unit": "inch", + "x_resolution": 72, "y_resolution": 36} filename = self.tempfile("temp.tif") hopper("RGB").save(filename, **kwargs) im = Image.open(filename) @@ -477,7 +487,8 @@ def test_strip_raw(self): infile = "Tests/images/tiff_strip_raw.tif" im = Image.open(infile) - self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") + self.assert_image_equal_tofile( + im, "Tests/images/tiff_adobe_deflate.png") def test_strip_planar_raw(self): # gdal_translate -of GTiff -co INTERLEAVE=BAND \ @@ -485,14 +496,16 @@ def test_strip_planar_raw(self): infile = "Tests/images/tiff_strip_planar_raw.tif" im = Image.open(infile) - self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") + self.assert_image_equal_tofile( + im, "Tests/images/tiff_adobe_deflate.png") def test_strip_planar_raw_with_overviews(self): # gdaladdo tiff_strip_planar_raw2.tif 2 4 8 16 infile = "Tests/images/tiff_strip_planar_raw_with_overviews.tif" im = Image.open(infile) - self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") + self.assert_image_equal_tofile( + im, "Tests/images/tiff_adobe_deflate.png") def test_tiled_planar_raw(self): # gdal_translate -of GTiff -co TILED=YES -co BLOCKXSIZE=32 \ @@ -501,7 +514,8 @@ def test_tiled_planar_raw(self): infile = "Tests/images/tiff_tiled_planar_raw.tif" im = Image.open(infile) - self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") + self.assert_image_equal_tofile( + im, "Tests/images/tiff_adobe_deflate.png") def test_palette(self): for mode in ["P", "PA"]: @@ -528,7 +542,8 @@ def test_tiff_save_all(self): # Test appending images mp = io.BytesIO() im = Image.new("RGB", (100, 100), "#f00") - ims = [Image.new("RGB", (100, 100), color) for color in ["#0f0", "#00f"]] + ims = [Image.new("RGB", (100, 100), color) + for color in ["#0f0", "#00f"]] im.copy().save(mp, format="TIFF", save_all=True, append_images=ims) mp.seek(0, os.SEEK_SET) @@ -541,7 +556,8 @@ def imGenerator(ims): yield im mp = io.BytesIO() - im.save(mp, format="TIFF", save_all=True, append_images=imGenerator(ims)) + im.save(mp, format="TIFF", save_all=True, + append_images=imGenerator(ims)) mp.seek(0, os.SEEK_SET) reread = Image.open(mp) @@ -607,7 +623,6 @@ def test_fd_leak(self): "Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif", ], ) - @pytest.mark.timeout(2) def test_oom(self, test_file): with pytest.raises(UnidentifiedImageError): with Image.open(test_file) as im: