From 0840b74dcdd2b9d484ae71c5579dcc3e3bf2a017 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 22 Oct 2026 11:22:09 -0700 Subject: [PATCH] gh-159739: Allocate thread-local state before constructing the object (GH-160166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If allocating a thread-local object’s internal dictionary or watchdog set fails, a subclass finalizer can run with that state missing. Accessing a thread-local attribute then dereferences the missing state and crashes; allocate both containers before creating the object. Backport to 3.15 and 3.14. (cherry picked from commit 4a61673276361aa4f8e86a7261d818a83f99b11e) Co-authored-by: Jelle Zijlstra --- ...-10-21-19-55-00.gh-issue-159739.tloc01.rst | 2 ++ Modules/_threadmodule.c | 25 +++++++++++-------- 2 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-21-19-55-00.gh-issue-159739.tloc01.rst diff --git a/Misc/NEWS.d/next/Library/2026-10-21-19-55-00.gh-issue-159739.tloc01.rst b/Misc/NEWS.d/next/Library/2026-10-21-19-55-00.gh-issue-159739.tloc01.rst new file mode 100644 index 00000000000000..9641d11deb84d6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-21-19-55-00.gh-issue-159739.tloc01.rst @@ -0,0 +1,2 @@ +Avoid a crash when a :class:`threading.local` subclass finalizer accesses +attributes after construction fails. diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 199e4ac3db723b..81ff9af1bd97c6 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -1521,10 +1521,25 @@ local_new(PyTypeObject *type, PyObject *args, PyObject *kw) assert(module != NULL); thread_module_state *state = get_thread_state(module); + // A subclass finalizer may access attributes if construction fails. + PyObject *localdicts = PyDict_New(); + if (localdicts == NULL) { + return NULL; + } + PyObject *thread_watchdogs = PySet_New(NULL); + if (thread_watchdogs == NULL) { + Py_DECREF(localdicts); + return NULL; + } + localobject *self = (localobject *)type->tp_alloc(type, 0); if (self == NULL) { + Py_DECREF(localdicts); + Py_DECREF(thread_watchdogs); return NULL; } + self->localdicts = localdicts; + self->thread_watchdogs = thread_watchdogs; // gh-128691: Use deferred reference counting for thread-locals to avoid // contention on the shared object. @@ -1533,16 +1548,6 @@ local_new(PyTypeObject *type, PyObject *args, PyObject *kw) self->args = Py_XNewRef(args); self->kw = Py_XNewRef(kw); - self->localdicts = PyDict_New(); - if (self->localdicts == NULL) { - goto err; - } - - self->thread_watchdogs = PySet_New(NULL); - if (self->thread_watchdogs == NULL) { - goto err; - } - PyObject *localsdict = NULL; PyObject *sentinel_wr = NULL; if (create_localsdict(self, state, &localsdict, &sentinel_wr) < 0) {