diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index ad8acde4938..8d230eb564c 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -51,6 +51,7 @@ def test_path() -> None: [0.0, 1.0], ((0, 1),), [(0, 1)], + [[0, 1]], ((0.0, 1.0),), [(0.0, 1.0)], array.array("f", [0, 1]), @@ -68,6 +69,34 @@ def test_path_constructors( assert list(p) == [(0.0, 1.0)] +@pytest.mark.parametrize( + "coords, expected", + ( + ([[0, 1], [2, 3]], [(0.0, 1.0), (2.0, 3.0)]), + ([[0.0, 1.0], [2.0, 3.0]], [(0.0, 1.0), (2.0, 3.0)]), + ), +) +def test_path_list_of_lists( + coords: list[list[float]], expected: list[tuple[float, float]] +) -> None: + p = ImagePath.Path(coords) + assert list(p) == expected + + +@pytest.mark.parametrize( + "coords, message", + ( + ([[1, 2, 3]], "coordinate list must contain exactly 2 coordinates"), + ([[1]], "coordinate list must contain exactly 2 coordinates"), + ([[[1, 2], [3, 4]]], "coordinate list must contain numbers"), + ([["a", "b"]], "coordinate list must contain numbers"), + ), +) +def test_invalid_list_coords(coords: list[list[object]], message: str) -> None: + with pytest.raises(ValueError, match=message): + ImagePath.Path(coords) + + def test_invalid_path_constructors() -> None: # Arrange / Act with pytest.raises(ValueError, match="incorrect coordinate type"): diff --git a/docs/releasenotes/12.2.0.rst b/docs/releasenotes/12.2.0.rst index 05d5dee2567..b03afb6651f 100644 --- a/docs/releasenotes/12.2.0.rst +++ b/docs/releasenotes/12.2.0.rst @@ -33,6 +33,16 @@ Integer overflow when processing fonts If a font advances for each glyph by an exceeding large amount, when Pillow keeps track of the current position, it may lead to an integer overflow. This has been fixed. +Heap buffer overflow with nested list coordinates +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Passing nested lists as coordinates to APIs that accept coordinates such as +``ImagePath.Path``, :py:meth:`~PIL.ImageDraw.ImageDraw.polygon` +and :py:meth:`~PIL.ImageDraw.ImageDraw.line` could cause a heap buffer overflow, +as nested lists were recursively unpacked beyond the allocated buffer. +Coordinate lists are now validated to contain exactly two numeric coordinates. +This was introduced in Pillow 11.2.1. + API changes =========== diff --git a/src/path.c b/src/path.c index 38300547c60..b88346d5f8f 100644 --- a/src/path.c +++ b/src/path.c @@ -118,14 +118,27 @@ assign_item_to_array(double *xy, Py_ssize_t j, PyObject *op) { } else if (PyNumber_Check(op)) { xy[j++] = PyFloat_AsDouble(op); } else if (PyList_Check(op)) { + if (PyList_GET_SIZE(op) != 2) { + PyErr_SetString( + PyExc_ValueError, "coordinate list must contain exactly 2 coordinates" + ); + return -1; + } for (int k = 0; k < 2; k++) { PyObject *op1 = PyList_GetItemRef(op, k); if (op1 == NULL) { return -1; } - j = assign_item_to_array(xy, j, op1); + if (PyFloat_Check(op1) || PyLong_Check(op1) || PyNumber_Check(op1)) { + j = assign_item_to_array(xy, j, op1); + } else { + j = -1; + } Py_DECREF(op1); if (j == -1) { + PyErr_SetString( + PyExc_ValueError, "coordinate list must contain numbers" + ); return -1; } }