diff --git a/CHANGES.rst b/CHANGES.rst index 3edeee836..11a75f301 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,28 @@ Changelog (Pillow) ================== +6.2.2.4 (date TBD) +------------------ + +- Use more specific regex chars to prevent ReDoS. CVE-2021-25292 + [rickprice,hugovk] + +- Fix CVE CVE-2021-25293: There is an out-of-bounds read in ``SgiRleDecode.c``, +since Pillow 4.3.0. + [rickprice] + +- Fix CVE-2021-2791 + [rickprice] + +6.2.2.3 (2023-02-23) +------------------ + +- CVE-2022-22817 Restrict builtins for ImageMath.eval() + [rickprice] + +- CVE-2022-24303 Pillow before 9.0.1 allows attackers to delete files because spaces in temporary pathnames are mishandled. + [rickprice] + 6.2.2.2 (date TBD) ------------------ @@ -12,7 +34,7 @@ Changelog (Pillow) - Use snprintf instead of sprintf. CVE-2021-34552 [wooken] - + 6.2.2.1 (2021-10-08) ------------------ diff --git a/Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns b/Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns new file mode 100644 index 000000000..0521f5cf1 Binary files /dev/null and b/Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns differ diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index 2e33e0ae5..d5f7aa560 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -1,120 +1,147 @@ import io import sys -from PIL import IcnsImagePlugin, Image +import pytest -from .helper import PillowTestCase, unittest +from PIL import IcnsImagePlugin, Image, features # sample icon file TEST_FILE = "Tests/images/pillow.icns" -enable_jpeg2k = hasattr(Image.core, "jp2klib_version") +ENABLE_JPEG2K = features.check_codec("jpg_2000") -class TestFileIcns(PillowTestCase): - def test_sanity(self): - # Loading this icon by default should result in the largest size - # (512x512@2x) being loaded - im = Image.open(TEST_FILE) +def test_sanity(): + # Loading this icon by default should result in the largest size + # (512x512@2x) being loaded + with Image.open(TEST_FILE) as im: # Assert that there is no unclosed file warning - self.assert_warning(None, im.load) + with pytest.warns(None) as record: + im.load() + assert not record - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (1024, 1024)) - self.assertEqual(im.format, "ICNS") + assert im.mode == "RGBA" + assert im.size == (1024, 1024) + assert im.format == "ICNS" - @unittest.skipIf(sys.platform != "darwin", "requires macOS") - def test_save(self): - im = Image.open(TEST_FILE) - temp_file = self.tempfile("temp.icns") +@pytest.mark.skipif(sys.platform != "darwin", reason="Requires macOS") +def test_save(tmp_path): + temp_file = str(tmp_path / "temp.icns") + + with Image.open(TEST_FILE) as im: im.save(temp_file) - reread = Image.open(temp_file) + with Image.open(temp_file) as reread: + assert reread.mode == "RGBA" + assert reread.size == (1024, 1024) + assert reread.format == "ICNS" - self.assertEqual(reread.mode, "RGBA") - self.assertEqual(reread.size, (1024, 1024)) - self.assertEqual(reread.format, "ICNS") - @unittest.skipIf(sys.platform != "darwin", "requires macOS") - def test_save_append_images(self): - im = Image.open(TEST_FILE) +@pytest.mark.skipif(sys.platform != "darwin", reason="Requires macOS") +def test_save_append_images(tmp_path): + temp_file = str(tmp_path / "temp.icns") + provided_im = Image.new("RGBA", (32, 32), (255, 0, 0, 128)) - temp_file = self.tempfile("temp.icns") - provided_im = Image.new("RGBA", (32, 32), (255, 0, 0, 128)) + with Image.open(TEST_FILE) as im: im.save(temp_file, append_images=[provided_im]) - reread = Image.open(temp_file) - self.assert_image_similar(reread, im, 1) + assert_image_similar_tofile(im, temp_file, 1) - reread = Image.open(temp_file) - reread.size = (16, 16, 2) - reread.load() - self.assert_image_equal(reread, provided_im) + with Image.open(temp_file) as reread: + reread.size = (16, 16, 2) + reread.load() + assert_image_equal(reread, provided_im) - def test_sizes(self): - # Check that we can load all of the sizes, and that the final pixel - # dimensions are as expected - im = Image.open(TEST_FILE) + +@pytest.mark.skipif(sys.platform != "darwin", reason="Requires macOS") +def test_save_fp(): + fp = io.BytesIO() + + with Image.open(TEST_FILE) as im: + im.save(fp, format="ICNS") + + with Image.open(fp) as reread: + assert reread.mode == "RGBA" + assert reread.size == (1024, 1024) + assert reread.format == "ICNS" + + +def test_sizes(): + # Check that we can load all of the sizes, and that the final pixel + # dimensions are as expected + with Image.open(TEST_FILE) as im: for w, h, r in im.info["sizes"]: wr = w * r hr = h * r im.size = (w, h, r) im.load() - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (wr, hr)) + assert im.mode == "RGBA" + assert im.size == (wr, hr) # Check that we cannot load an incorrect size - with self.assertRaises(ValueError): + with pytest.raises(ValueError): im.size = (1, 1) - def test_older_icon(self): - # This icon was made with Icon Composer rather than iconutil; it still - # uses PNG rather than JP2, however (since it was made on 10.9). - im = Image.open("Tests/images/pillow2.icns") + +def test_older_icon(): + # This icon was made with Icon Composer rather than iconutil; it still + # uses PNG rather than JP2, however (since it was made on 10.9). + with Image.open("Tests/images/pillow2.icns") as im: for w, h, r in im.info["sizes"]: wr = w * r hr = h * r - im2 = Image.open("Tests/images/pillow2.icns") - im2.size = (w, h, r) - im2.load() - self.assertEqual(im2.mode, "RGBA") - self.assertEqual(im2.size, (wr, hr)) + with Image.open("Tests/images/pillow2.icns") as im2: + im2.size = (w, h, r) + im2.load() + assert im2.mode == "RGBA" + assert im2.size == (wr, hr) - def test_jp2_icon(self): - # This icon was made by using Uli Kusterer's oldiconutil to replace - # the PNG images with JPEG 2000 ones. The advantage of doing this is - # that OS X 10.5 supports JPEG 2000 but not PNG; some commercial - # software therefore does just this. - # (oldiconutil is here: https://github.com/uliwitness/oldiconutil) +def test_jp2_icon(): + # This icon was made by using Uli Kusterer's oldiconutil to replace + # the PNG images with JPEG 2000 ones. The advantage of doing this is + # that OS X 10.5 supports JPEG 2000 but not PNG; some commercial + # software therefore does just this. - if not enable_jpeg2k: - return + # (oldiconutil is here: https://github.com/uliwitness/oldiconutil) - im = Image.open("Tests/images/pillow3.icns") + if not ENABLE_JPEG2K: + return + + with Image.open("Tests/images/pillow3.icns") as im: for w, h, r in im.info["sizes"]: wr = w * r hr = h * r - im2 = Image.open("Tests/images/pillow3.icns") - im2.size = (w, h, r) - im2.load() - self.assertEqual(im2.mode, "RGBA") - self.assertEqual(im2.size, (wr, hr)) + with Image.open("Tests/images/pillow3.icns") as im2: + im2.size = (w, h, r) + im2.load() + assert im2.mode == "RGBA" + assert im2.size == (wr, hr) - def test_getimage(self): - with open(TEST_FILE, "rb") as fp: - icns_file = IcnsImagePlugin.IcnsFile(fp) - im = icns_file.getimage() - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (1024, 1024)) +def test_getimage(): + with open(TEST_FILE, "rb") as fp: + icns_file = IcnsImagePlugin.IcnsFile(fp) - im = icns_file.getimage((512, 512)) - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (512, 512)) + im = icns_file.getimage() + assert im.mode == "RGBA" + assert im.size == (1024, 1024) - def test_not_an_icns_file(self): - with io.BytesIO(b"invalid\n") as fp: - self.assertRaises(SyntaxError, IcnsImagePlugin.IcnsFile, fp) + im = icns_file.getimage((512, 512)) + assert im.mode == "RGBA" + assert im.size == (512, 512) + + +def test_not_an_icns_file(): + with io.BytesIO(b"invalid\n") as fp: + with pytest.raises(SyntaxError): + IcnsImagePlugin.IcnsFile(fp) + + +def test_icns_decompression_bomb(): + with pytest.raises(Image.DecompressionBombError): + im = Image.open( + 'Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns') + im.load() diff --git a/Tests/test_file_icns.py.orig b/Tests/test_file_icns.py.orig new file mode 100644 index 000000000..e13569388 --- /dev/null +++ b/Tests/test_file_icns.py.orig @@ -0,0 +1,138 @@ +import io +import sys + +from PIL import IcnsImagePlugin, Image + +from .helper import PillowTestCase, unittest + +# sample icon file +TEST_FILE = "Tests/images/pillow.icns" + +enable_jpeg2k = hasattr(Image.core, "jp2klib_version") + + +class TestFileIcns(PillowTestCase): + def test_sanity(self): + # Loading this icon by default should result in the largest size + # (512x512@2x) being loaded + im = Image.open(TEST_FILE) + + # Assert that there is no unclosed file warning + self.assert_warning(None, im.load) + + self.assertEqual(im.mode, "RGBA") + self.assertEqual(im.size, (1024, 1024)) + self.assertEqual(im.format, "ICNS") + + @unittest.skipIf(sys.platform != "darwin", "requires macOS") + def test_save(self): + im = Image.open(TEST_FILE) + + temp_file = self.tempfile("temp.icns") + im.save(temp_file) + + reread = Image.open(temp_file) + + self.assertEqual(reread.mode, "RGBA") + self.assertEqual(reread.size, (1024, 1024)) + self.assertEqual(reread.format, "ICNS") + + @unittest.skipIf(sys.platform != "darwin", "requires macOS") + def test_save_append_images(self): + im = Image.open(TEST_FILE) + + temp_file = self.tempfile("temp.icns") + provided_im = Image.new("RGBA", (32, 32), (255, 0, 0, 128)) + im.save(temp_file, append_images=[provided_im]) + + reread = Image.open(temp_file) + self.assert_image_similar(reread, im, 1) + + reread = Image.open(temp_file) + reread.size = (16, 16, 2) + reread.load() + self.assert_image_equal(reread, provided_im) + + def test_sizes(self): + # Check that we can load all of the sizes, and that the final pixel + # dimensions are as expected + im = Image.open(TEST_FILE) + for w, h, r in im.info["sizes"]: + wr = w * r + hr = h * r + im.size = (w, h, r) + im.load() + self.assertEqual(im.mode, "RGBA") + self.assertEqual(im.size, (wr, hr)) + + # Check that we cannot load an incorrect size + with self.assertRaises(ValueError): + im.size = (1, 1) + + def test_older_icon(self): + # This icon was made with Icon Composer rather than iconutil; it still + # uses PNG rather than JP2, however (since it was made on 10.9). + im = Image.open("Tests/images/pillow2.icns") + for w, h, r in im.info["sizes"]: + wr = w * r + hr = h * r + im2 = Image.open("Tests/images/pillow2.icns") + im2.size = (w, h, r) + im2.load() + self.assertEqual(im2.mode, "RGBA") + self.assertEqual(im2.size, (wr, hr)) + + def test_jp2_icon(self): + # This icon was made by using Uli Kusterer's oldiconutil to replace + # the PNG images with JPEG 2000 ones. The advantage of doing this is + # that OS X 10.5 supports JPEG 2000 but not PNG; some commercial + # software therefore does just this. + + # (oldiconutil is here: https://github.com/uliwitness/oldiconutil) + + if not enable_jpeg2k: + return + + im = Image.open("Tests/images/pillow3.icns") + for w, h, r in im.info["sizes"]: + wr = w * r + hr = h * r + im2 = Image.open("Tests/images/pillow3.icns") + im2.size = (w, h, r) + im2.load() + self.assertEqual(im2.mode, "RGBA") + self.assertEqual(im2.size, (wr, hr)) + + def test_getimage(self): + with open(TEST_FILE, "rb") as fp: + icns_file = IcnsImagePlugin.IcnsFile(fp) + + im = icns_file.getimage() + self.assertEqual(im.mode, "RGBA") + self.assertEqual(im.size, (1024, 1024)) + + im = icns_file.getimage((512, 512)) + self.assertEqual(im.mode, "RGBA") + self.assertEqual(im.size, (512, 512)) + +<<<<<<< HEAD + def test_not_an_icns_file(self): + with io.BytesIO(b"invalid\n") as fp: + self.assertRaises(SyntaxError, IcnsImagePlugin.IcnsFile, fp) +======= + im = icns_file.getimage((512, 512)) + assert im.mode == "RGBA" + assert im.size == (512, 512) + + +def test_not_an_icns_file(): + with io.BytesIO(b"invalid\n") as fp: + with pytest.raises(SyntaxError): + IcnsImagePlugin.IcnsFile(fp) + + +def test_icns_decompression_bomb(): + with pytest.raises(Image.DecompressionBombError): + im = Image.open('Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns') + im.load() +>>>>>>> 480f6819b (Fix Memory DOS in Icns, Ico and Blp Image Plugins) diff --git a/docs/releasenotes/6.2.2.4.rst b/docs/releasenotes/6.2.2.4.rst new file mode 100644 index 000000000..fb8844a70 --- /dev/null +++ b/docs/releasenotes/6.2.2.4.rst @@ -0,0 +1,12 @@ +6.2.2.4 +------- + +Security +======== + +This release addresses several critical CVEs. + +:cve:`CVE-2021-25293`: There is an out-of-bounds read in ``SgiRleDecode.c``, +since Pillow 4.3.0. + +:cve: `CVE-2021-2791` : Pillow before 8.1.1 allows attackers to cause a denial of service (memory consumption) because the reported size of a contained image is not properly checked for a BLP container, and thus an attempted memory allocation can be very large. diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index dd1745158..7f54fb337 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -353,6 +353,7 @@ class BLP1Decoder(_BLPBaseDecoder): data = jpeg_header + data data = BytesIO(data) image = JpegImageFile(data) + Image._decompression_bomb_check(image.size) self.tile = image.tile # :/ self.fd = image.fp self.mode = image.mode diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index 75ea18b6b..fa3dd6ac5 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -105,6 +105,7 @@ def read_png_or_jpeg2000(fobj, start_length, size): if sig[:8] == b"\x89PNG\x0d\x0a\x1a\x0a": fobj.seek(start) im = PngImagePlugin.PngImageFile(fobj) + Image._decompression_bomb_check(im.size) return {"RGBA": im} elif ( sig[:4] == b"\xff\x4f\xff\x51" @@ -121,6 +122,7 @@ def read_png_or_jpeg2000(fobj, start_length, size): jp2kstream = fobj.read(length) f = io.BytesIO(jp2kstream) im = Jpeg2KImagePlugin.Jpeg2KImageFile(f) + Image._decompression_bomb_check(im.size) if im.mode != "RGBA": im = im.convert("RGBA") return {"RGBA": im} diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index 148e604f8..0222d1573 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -177,6 +177,7 @@ class IcoFile(object): if data[:8] == PngImagePlugin._MAGIC: # png frame im = PngImagePlugin.PngImageFile(self.buf) + Image._decompression_bomb_check(im.size) else: # XOR + AND mask bmp frame im = BmpImagePlugin.DibImageFile(self.buf)