Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions Tests/test_image_point.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import pytest

from PIL import Image

from .helper import assert_image_equal, hopper


Expand Down Expand Up @@ -62,3 +64,36 @@ def test_f_mode() -> None:
im = hopper("F")
with pytest.raises(ValueError):
im.point([])


def test_overstated_length() -> None:
# shouldn't segfault
# see https://github.com/python-pillow/Pillow/issues/9892

class OverstatedLengthSequence:
def __len__(self) -> int:
return 256

def __getitem__(self, index: int) -> float:
if index >= 8:
raise IndexError
return float(index)

im = Image.new("L", (4, 4))
with pytest.raises(ValueError):
im.point(OverstatedLengthSequence(), "F") # type: ignore[arg-type]


def test_unsized_sequence() -> None:
# a table that only implements __getitem__ (no __len__) is a common shape
# for custom point tables; PySequence_Size() fails for it, so the cheap
# pre-check must fall back to materializing instead of erroring out
class UnsizedSequence:
def __getitem__(self, index: int) -> float:
if index >= 256:
raise IndexError
return float(index)

im = Image.new("L", (4, 4))
out = im.point(UnsizedSequence(), "F") # type: ignore[arg-type]
assert_image_equal(out, im.point(list(range(256)), "F"))
41 changes: 41 additions & 0 deletions Tests/test_image_putdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,47 @@ def test_array_F() -> None:
assert len(im.get_flattened_data()) == len(arr)


def test_overstated_length() -> None:
# shouldn't segfault
# see https://github.com/python-pillow/Pillow/issues/9892

class OverstatedLengthSequence:
def __len__(self) -> int:
return 16

def __getitem__(self, index: int) -> float:
if index >= 2:
raise IndexError
return float(index + 1)

im = Image.new("L", (4, 4))
im.putdata(OverstatedLengthSequence()) # type: ignore[arg-type]
assert im.get_flattened_data()[:2] == (1, 2)


def test_too_many_entries() -> None:
# an honest, correctly-reported sequence that is simply longer than the
# image still has to be rejected, before or after materialization
im = Image.new("L", (4, 4))
with pytest.raises(TypeError):
im.putdata(list(range(17)))


def test_unsized_sequence() -> None:
# a sequence that only implements __getitem__ (no __len__) is common for
# custom point-table-like objects; PySequence_Size() fails for it, so the
# cheap pre-check must fall back to materializing instead of erroring out
class UnsizedSequence:
def __getitem__(self, index: int) -> int:
if index >= 4:
raise IndexError
return index

im = Image.new("L", (2, 2))
im.putdata(UnsizedSequence()) # type: ignore[arg-type]
assert im.get_flattened_data() == (0, 1, 2, 3)


def test_not_flattened() -> None:
im = Image.new("L", (1, 1))
with pytest.raises(TypeError):
Expand Down
55 changes: 37 additions & 18 deletions src/_imaging.c
Original file line number Diff line number Diff line change
Expand Up @@ -442,25 +442,36 @@ getlist(PyObject *arg, Py_ssize_t *length, const char *wrong_length, int type) {
return NULL;
}

n = PySequence_Size(arg);
if (length && wrong_length) {
Py_ssize_t reported = PySequence_Size(arg);
if (reported < 0) {
PyErr_Clear();
} else if (reported != *length) {
PyErr_SetString(PyExc_ValueError, wrong_length);
return NULL;
}
}

seq = PySequence_Fast(arg, must_be_sequence);
if (!seq) {
return NULL;
}

n = PySequence_Fast_GET_SIZE(seq);
if (length && wrong_length && n != *length) {
PyErr_SetString(PyExc_ValueError, wrong_length);
Py_DECREF(seq);
return NULL;
}

/* malloc check ok, type & ff is just a sizeof(something)
calloc checks for overflow */
list = calloc(n, type & 0xff);
if (!list) {
Py_DECREF(seq);
return ImagingError_MemoryError();
}

seq = PySequence_Fast(arg, must_be_sequence);
if (!seq) {
free(list);
return NULL;
}

for (i = 0; i < n; i++) {
op = PySequence_Fast_GET_ITEM(seq, i);
// DRY, branch prediction is going to work _really_ well
Expand Down Expand Up @@ -1625,8 +1636,26 @@ _putdata(ImagingObject *self, PyObject *args) {

image = self->image;

n = PyObject_Length(data);
if (image->image8 && PyBytes_Check(data)) {
n = PyBytes_GET_SIZE(data);
} else {
Py_ssize_t reported = PySequence_Size(data);
if (reported < 0) {
PyErr_Clear();
} else if (reported > (Py_ssize_t)image->xsize * (Py_ssize_t)image->ysize) {
PyErr_SetString(PyExc_TypeError, "too many data entries");
return NULL;
}

seq = PySequence_Fast(data, must_be_sequence);
if (!seq) {
PyErr_SetString(PyExc_TypeError, must_be_sequence);
return NULL;
}
n = PySequence_Fast_GET_SIZE(seq);
}
if (n > (Py_ssize_t)image->xsize * (Py_ssize_t)image->ysize) {
Py_XDECREF(seq);
PyErr_SetString(PyExc_TypeError, "too many data entries");
return NULL;
}
Expand Down Expand Up @@ -1667,11 +1696,6 @@ _putdata(ImagingObject *self, PyObject *args) {
}
}
} else {
seq = PySequence_Fast(data, must_be_sequence);
if (!seq) {
PyErr_SetString(PyExc_TypeError, must_be_sequence);
return NULL;
}
double value;
int bigendian = 0;
if (image->type == IMAGING_TYPE_SPECIAL) {
Expand Down Expand Up @@ -1705,11 +1729,6 @@ _putdata(ImagingObject *self, PyObject *args) {
}
} else {
/* 32-bit images */
seq = PySequence_Fast(data, must_be_sequence);
if (!seq) {
PyErr_SetString(PyExc_TypeError, must_be_sequence);
return NULL;
}
switch (image->type) {
case IMAGING_TYPE_INT32:
for (i = x = y = 0; i < n; i++) {
Expand Down
Loading