diff --git a/CHANGES.rst b/CHANGES.rst index 6bb23e5f7..7f3fda653 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,6 +12,10 @@ Changelog (Pillow) Add test files that show the CVE was fixed [rickprice] +- Fix CVE-2022-22815, CVE-2022-22816 + Fixed ImagePath.Path array handling + [rickprice] + 6.2.2.4 (2023-03-29) ------------------ diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index ed65d47c1..9643b783d 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -1,5 +1,7 @@ import array import struct +import pytest +import math from PIL import Image, ImagePath from PIL._util import py3 @@ -19,14 +21,17 @@ class TestImagePath(PillowTestCase): self.assertEqual(list(p[:1]), [(0.0, 1.0)]) with self.assertRaises(TypeError) as cm: p["foo"] - self.assertEqual(str(cm.exception), "Path indices must be integers, not str") + self.assertEqual(str(cm.exception), + "Path indices must be integers, not str") self.assertEqual( - list(p), [(0.0, 1.0), (2.0, 3.0), (4.0, 5.0), (6.0, 7.0), (8.0, 9.0)] + list(p), [(0.0, 1.0), (2.0, 3.0), + (4.0, 5.0), (6.0, 7.0), (8.0, 9.0)] ) # method sanity check self.assertEqual( - p.tolist(), [(0.0, 1.0), (2.0, 3.0), (4.0, 5.0), (6.0, 7.0), (8.0, 9.0)] + p.tolist(), [(0.0, 1.0), (2.0, 3.0), + (4.0, 5.0), (6.0, 7.0), (8.0, 9.0)] ) self.assertEqual( p.tolist(1), [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] @@ -81,6 +86,109 @@ class TestImagePath(PillowTestCase): x[i] = "0" * 16 +def test_path_odd_number_of_coordinates(): + # Arrange + coords = [0] + + # Act / Assert + with pytest.raises(ValueError) as e: + ImagePath.Path(coords) + + assert str(e.value) == "wrong number of coordinates" + + +def test_getbbox(): + for coords, expected in [ + ([0, 1, 2, 3], (0.0, 1.0, 2.0, 3.0)), + ([3, 2, 1, 0], (1.0, 0.0, 3.0, 2.0)), + (0, (0.0, 0.0, 0.0, 0.0)), + (1, (0.0, 0.0, 0.0, 0.0)), + ]: + + # Arrange + p = ImagePath.Path(coords) + + # Act / Assert + assert p.getbbox() == expected + + +def test_getbbox_no_args(): + # Arrange + p = ImagePath.Path([0, 1, 2, 3]) + + # Act / Assert + with pytest.raises(TypeError): + p.getbbox(1) + + +def test_map(): + for coords, expected in [ + (0, []), + (list(range(6)), [(0.0, 3.0), (4.0, 9.0), (8.0, 15.0)]), + ]: + # Arrange + p = ImagePath.Path(coords) + + # Act + # Modifies the path in-place + p.map(lambda x, y: (x * 2, y * 3)) + + # Assert + assert list(p) == expected + + +def test_transform(): + # Arrange + p = ImagePath.Path([0, 1, 2, 3]) + theta = math.pi / 15 + + # Act + # Affine transform, in-place + p.transform( + (math.cos(theta), math.sin(theta), 20, - + math.sin(theta), math.cos(theta), 20), + ) + + # Assert + assert p.tolist() == [ + (20.20791169081776, 20.978147600733806), + (22.58003027392089, 22.518619420565898), + ] + + +def test_transform_with_wrap(): + # Arrange + p = ImagePath.Path([0, 1, 2, 3]) + theta = math.pi / 15 + + # Act + # Affine transform, in-place, with wrap parameter + p.transform( + (math.cos(theta), math.sin(theta), 20, - + math.sin(theta), math.cos(theta), 20), + 1.0, + ) + + # Assert + assert p.tolist() == [ + (0.20791169081775962, 20.978147600733806), + (0.5800302739208902, 22.518619420565898), + ] + + +def test_overflow_segfault(): + # Some Pythons fail getting the argument as an integer, and it falls + # through to the sequence. Seeing this on 32-bit Windows. + with pytest.raises((TypeError, MemoryError)): + # post patch, this fails with a memory error + x = evil() + + # This fails due to the invalid malloc above, + # and segfaults + for i in range(200000): + x[i] = b"0" * 16 + + class evil: def __init__(self): self.corrupt = Image.core.path(0x4000000000000000) diff --git a/docs/releasenotes/6.2.2.5.rst b/docs/releasenotes/6.2.2.5.rst index 7e25cc233..6ad06f940 100644 --- a/docs/releasenotes/6.2.2.5.rst +++ b/docs/releasenotes/6.2.2.5.rst @@ -10,3 +10,7 @@ This release addresses several critical CVEs. :cve:`CVE-2021-25289`: Catch TiffDecode heap-based buffer overflow. Add test files that show the CVE was fixed +:cve:`CVE-2022-22815`: Fixed ImagePath.Path array handling + +:cve:`CVE-2022-22816`: Fixed ImagePath.Path array handling + diff --git a/src/path.c b/src/path.c index 5f0541b0b..84a3f4c46 100644 --- a/src/path.c +++ b/src/path.c @@ -62,9 +62,10 @@ alloc_array(Py_ssize_t count) PyErr_NoMemory(); return NULL; } - xy = malloc(2 * count * sizeof(double) + 1); - if (!xy) - PyErr_NoMemory(); + xy = calloc(2 * count * sizeof(double) + 1, sizeof(double)); + if (!xy) { + ImagingError_MemoryError(); + } return xy; } @@ -330,18 +331,27 @@ path_getbbox(PyPathObject* self, PyObject* args) xy = self->xy; - x0 = x1 = xy[0]; - y0 = y1 = xy[1]; + if (self->count == 0) { + x0 = x1 = 0; + y0 = y1 = 0; + } else { + x0 = x1 = xy[0]; + y0 = y1 = xy[1]; - for (i = 1; i < self->count; i++) { - if (xy[i+i] < x0) - x0 = xy[i+i]; - if (xy[i+i] > x1) - x1 = xy[i+i]; - if (xy[i+i+1] < y0) - y0 = xy[i+i+1]; - if (xy[i+i+1] > y1) - y1 = xy[i+i+1]; + for (i = 1; i < self->count; i++) { + if (xy[i + i] < x0) { + x0 = xy[i + i]; + } + if (xy[i + i] > x1) { + x1 = xy[i + i]; + } + if (xy[i + i + 1] < y0) { + y0 = xy[i + i + 1]; + } + if (xy[i + i + 1] > y1) { + y1 = xy[i + i + 1]; + } + } } return Py_BuildValue("dddd", x0, y0, x1, y1);