Skip to content

Commit cf6de8c

Browse files
authored
Reject non-numeric elements inside list coords (#9526)
1 parent ffdcede commit cf6de8c

File tree

3 files changed

+53
-1
lines changed

3 files changed

+53
-1
lines changed

Tests/test_imagepath.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def test_path() -> None:
5151
[0.0, 1.0],
5252
((0, 1),),
5353
[(0, 1)],
54+
[[0, 1]],
5455
((0.0, 1.0),),
5556
[(0.0, 1.0)],
5657
array.array("f", [0, 1]),
@@ -68,6 +69,34 @@ def test_path_constructors(
6869
assert list(p) == [(0.0, 1.0)]
6970

7071

72+
@pytest.mark.parametrize(
73+
"coords, expected",
74+
(
75+
([[0, 1], [2, 3]], [(0.0, 1.0), (2.0, 3.0)]),
76+
([[0.0, 1.0], [2.0, 3.0]], [(0.0, 1.0), (2.0, 3.0)]),
77+
),
78+
)
79+
def test_path_list_of_lists(
80+
coords: list[list[float]], expected: list[tuple[float, float]]
81+
) -> None:
82+
p = ImagePath.Path(coords)
83+
assert list(p) == expected
84+
85+
86+
@pytest.mark.parametrize(
87+
"coords, message",
88+
(
89+
([[1, 2, 3]], "coordinate list must contain exactly 2 coordinates"),
90+
([[1]], "coordinate list must contain exactly 2 coordinates"),
91+
([[[1, 2], [3, 4]]], "coordinate list must contain numbers"),
92+
([["a", "b"]], "coordinate list must contain numbers"),
93+
),
94+
)
95+
def test_invalid_list_coords(coords: list[list[object]], message: str) -> None:
96+
with pytest.raises(ValueError, match=message):
97+
ImagePath.Path(coords)
98+
99+
71100
def test_invalid_path_constructors() -> None:
72101
# Arrange / Act
73102
with pytest.raises(ValueError, match="incorrect coordinate type"):

docs/releasenotes/12.2.0.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ Integer overflow when processing fonts
3333
If a font advances for each glyph by an exceeding large amount, when Pillow keeps track
3434
of the current position, it may lead to an integer overflow. This has been fixed.
3535

36+
Heap buffer overflow with nested list coordinates
37+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
38+
39+
Passing nested lists as coordinates to APIs that accept coordinates such as
40+
``ImagePath.Path``, :py:meth:`~PIL.ImageDraw.ImageDraw.polygon`
41+
and :py:meth:`~PIL.ImageDraw.ImageDraw.line` could cause a heap buffer overflow,
42+
as nested lists were recursively unpacked beyond the allocated buffer.
43+
Coordinate lists are now validated to contain exactly two numeric coordinates.
44+
This was introduced in Pillow 11.2.1.
45+
3646
API changes
3747
===========
3848

src/path.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,27 @@ assign_item_to_array(double *xy, Py_ssize_t j, PyObject *op) {
118118
} else if (PyNumber_Check(op)) {
119119
xy[j++] = PyFloat_AsDouble(op);
120120
} else if (PyList_Check(op)) {
121+
if (PyList_GET_SIZE(op) != 2) {
122+
PyErr_SetString(
123+
PyExc_ValueError, "coordinate list must contain exactly 2 coordinates"
124+
);
125+
return -1;
126+
}
121127
for (int k = 0; k < 2; k++) {
122128
PyObject *op1 = PyList_GetItemRef(op, k);
123129
if (op1 == NULL) {
124130
return -1;
125131
}
126-
j = assign_item_to_array(xy, j, op1);
132+
if (PyFloat_Check(op1) || PyLong_Check(op1) || PyNumber_Check(op1)) {
133+
j = assign_item_to_array(xy, j, op1);
134+
} else {
135+
j = -1;
136+
}
127137
Py_DECREF(op1);
128138
if (j == -1) {
139+
PyErr_SetString(
140+
PyExc_ValueError, "coordinate list must contain numbers"
141+
);
129142
return -1;
130143
}
131144
}

0 commit comments

Comments
 (0)