From 7d2c5feeb28e111575a2a4c08e8c93b8316fd258 Mon Sep 17 00:00:00 2001 From: espressolee <70549809+espressolee@users.noreply.github.com> Date: Thu, 6 Aug 2026 02:40:16 +0900 Subject: [PATCH] Protect getlist() walk with a critical section (free-threading) getlist() (used by Image.point()) walks the caller's list via PySequence_Fast and the unchecked PySequence_Fast_GET_ITEM macro over a size captured earlier. On a free-threaded build a concurrent resize of that list drives an out-of-bounds read and a segfault (#9852). Hold a critical section on the fast sequence during the walk and clamp the loop to its current length, mirroring the approach used for FontObject in #9498. Co-Authored-By: Claude Opus 4.8 --- src/_imaging.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/_imaging.c b/src/_imaging.c index 9bdb6328782..ea2d502ed61 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -97,6 +97,8 @@ #include "libImaging/Imaging.h" +#include "thirdparty/pythoncapi_compat.h" + #define _USE_MATH_DEFINES #include #include @@ -461,7 +463,17 @@ getlist(PyObject *arg, Py_ssize_t *length, const char *wrong_length, int type) { return NULL; } - for (i = 0; i < n; i++) { + // On a free-threaded build PySequence_Fast returns the list itself for a list + // input, so the walk below aliases the caller's list. Hold a critical section on + // it and clamp to its current length, so a concurrent resize cannot make + // PySequence_Fast_GET_ITEM read out of bounds (#9852). `n` was read before this + // point and may be stale. + Py_BEGIN_CRITICAL_SECTION(seq); + Py_ssize_t count = PySequence_Fast_GET_SIZE(seq); + if (count > n) { + count = n; + } + for (i = 0; i < count; i++) { op = PySequence_Fast_GET_ITEM(seq, i); // DRY, branch prediction is going to work _really_ well // on this switch. And 3 fewer loops to copy/paste. @@ -484,6 +496,7 @@ getlist(PyObject *arg, Py_ssize_t *length, const char *wrong_length, int type) { break; } } + Py_END_CRITICAL_SECTION(); Py_DECREF(seq);