python-pillow / python-pillow/Pillow
getlist()/_putdata(): OOB read when __len__ overstates materialized items
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 13.8k
- Forks
- 2.5k
- Avg merge
- 2d 8h
- Merged PRs (30d)
- 89
Description
What did you do?
I passed a custom sequence to Image.point() and Image.putdata() whose __len__ reports more items than iteration actually materializes. This is independent of the free-threading race in #9852 / #9853: it is deterministic, single-threaded, and reproduces with the GIL enabled.
Image.point() reproducer:
from PIL import Image
class OverstatedLengthSequence:
def __len__(self):
return 256
def __getitem__(self, index):
if index >= 8:
raise IndexError
return float(index)
Image.new("L", (4, 4)).point(OverstatedLengthSequence(), "F")
Image.putdata() reproducer:
from PIL import Image
class OverstatedLengthSequence:
def __len__(self):
return 16
def __getitem__(self, index):
if index >= 2:
raise IndexError
return float(index + 1)
Image.new("L", (4, 4)).putdata(OverstatedLengthSequence())
What did you expect to happen?
Pillow should use the length of the sequence returned by PySequence_Fast():
- fixed-size consumers such as
Image.point()should raise their existing wrong-length error; Image.putdata()should consume only the items that were actually materialized.
It should not index past the end of the materialized list.
What actually happened?
On current main at
4e5f09f533dbd0d87d39951f48b62f4d1a421cba, each script terminated with SIGSEGV (exit 139) in 5/5 fresh processes on CPython 3.14.6 with the GIL enabled.
The two C paths first obtain n from the caller-controlled __len__, then materialize by iteration, but continue indexing to the earlier value of n:
/* getlist() */
n = PySequence_Size(arg);
seq = PySequence_Fast(arg, must_be_sequence);
for (i = 0; i < n; i++) {
op = PySequence_Fast_GET_ITEM(seq, i);
/* _putdata() */
n = PyObject_Length(data);
seq = PySequence_Fast(data, must_be_sequence);
for (i = 0; i < n; i++) {
op = PySequence_Fast_GET_ITEM(seq, i);
PySequence_Fast_GET_ITEM() is unchecked, so when n is 256 but seq contains 8 items (or 16 versus 2), the loop reads beyond the materialized list.
The same getlist() path is reachable through Image.transform() and ImageFilter.Kernel. JPEG qtables are not included: that path normalizes each table through array.array and list before the C code sees it.
I prepared an independent candidate at
41b675109723dcd0fab6f935518501992e0b26c6.
It materializes once, then derives n from PySequence_Fast_GET_SIZE(seq).
Against that exact commit:
- the
Image.point()reproducer raisesValueError: wrong number of lut entries; - the
Image.putdata()reproducer writes(1, 2)followed by 14 zeroes; - the affected-file suite passes 332/332 on CPython 3.14.6 (GIL enabled);
- the same suite passes 332/332 on CPython 3.14.0rc1t (GIL disabled).
This is reported as a regular robustness bug. The trigger is a Python object supplied by the caller; I am not assigning a security severity.
What are your OS, Python and Pillow versions?
- OS: macOS 26.6.1 (25G76), arm64
- Python: CPython 3.14.6, GIL enabled
- Pillow: 13.0.0.dev0,
mainat
4e5f09f533dbd0d87d39951f48b62f4d1a421cba
(Issue text and candidate developed with AI assistance; reproductions and measurements run by me.)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the C implementations of getlist() and _putdata() reached by Image.point(), Image.putdata(), Image.transform(), and ImageFilter.Kernel. Run both supplied reproducers and the affected-file suite, then verify that overstated len values no longer cause out-of-bounds reads, that point() raises its existing wrong-length error, and that putdata() consumes only materialized items.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, python
- Domain
- backend, computer-vision
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100