Skip to content

Fix OOB read in getlist() and putdata() when __len__ overstates length - #9893

Open
lazerg wants to merge 3 commits into
python-pillow:mainfrom
lazerg:fix/issue-9892-getlist-putdata-oob-read
Open

Fix OOB read in getlist() and putdata() when __len__ overstates length#9893
lazerg wants to merge 3 commits into
python-pillow:mainfrom
lazerg:fix/issue-9892-getlist-putdata-oob-read

Conversation

@lazerg

@lazerg lazerg commented Aug 23, 2026

Copy link
Copy Markdown

Fixes #9892.

Changes proposed in this pull request:

  • getlist() and _putdata() read the sequence length before calling PySequence_Fast(). A custom sequence whose __len__ reports more items than __getitem__ actually produces makes the later loop read past the materialized list, which segfaults.
  • Both functions now take the length from the materialized PySequence_Fast result instead, so they never index past what was actually built.

@akx akx left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the bigger issue is using PySequence_Fast things on custom types where __getitem__ can basically do whatever it likes...

I assume we'd see a similar crash for an object that raises IndexError randomly in __getitem__.

codecov/patch was failing at 64.71% because the self-review's cheap
PySequence_Size() pre-check (added to reject honest-but-oversized
sequences in O(1)) had no dedicated test: the "too many data entries"
TypeError path in _putdata() and the "no __len__" fallback path in both
getlist() and _putdata() (common for custom point-table-like objects)
were completely untested anywhere in the suite.

Add test_too_many_entries (putdata) and test_unsized_sequence (point,
putdata) to close those gaps; mirrors the existing test_overstated_length
convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R51WpQNzUqKreMEWv9TZtd
@lazerg

lazerg commented Aug 24, 2026

Copy link
Copy Markdown
Author

@akx I tested this directly: a __getitem__ that raises IndexError at random points doesn't crash after this fix.

PySequence_Fast on an object without __iter__ falls back to old-style sequence iteration. That protocol treats IndexError from __getitem__ as the normal end-of-sequence signal, not a propagated exception. It just stops there and materializes whatever came before. test_overstated_length already exercises this, just with a fixed cutoff instead of a random one.

The actual fix in this PR is that n now comes from PySequence_Fast_GET_SIZE(seq) after materialization, not from the caller's declared __len__. So it doesn't matter when IndexError fires. n always matches however many items got materialized. I ran 200 trials with __getitem__ raising IndexError at a 30% chance per index and got no crash or OOB read.

If __getitem__ raises something other than IndexError, CPython propagates it as a real exception from PySequence_Fast, and getlist()/_putdata() already bail out cleanly through if (!seq) return NULL;.

So this looks like the same bug class the PR already fixes, just probabilistic instead of fixed. Keeping this PR scoped to the reported OOB read. If you have a repro that still crashes on this branch, I'd like to see it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

getlist()/_putdata(): OOB read when __len__ overstates materialized items

2 participants