https://github.com/python/cpython/commit/784b305df8821d57fb6fb6c52794ef67bda0a75d
commit: 784b305df8821d57fb6fb6c52794ef67bda0a75d
branch: main
author: Jelle Zijlstra <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-10-22T18:24:53Z
summary:

gh-159739: Hold the codec registry lock while unregistering (#160168)

Concurrent calls to codecs.unregister() can shrink the search path between its 
length check and indexed lookup in a free-threaded build. The lookup then 
returns NULL and an unconditional decref crashes the interpreter; hold the 
registry mutex across the scan and removal. Also reported in gh-157078. 
Backport to 3.15 and 3.14.

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-10-21-19-55-00.gh-issue-159739.codc01.rst
M Python/codecs.c

diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-21-19-55-00.gh-issue-159739.codc01.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-21-19-55-00.gh-issue-159739.codc01.rst
new file mode 100644
index 00000000000000..985d4264befae2
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-21-19-55-00.gh-issue-159739.codc01.rst
@@ -0,0 +1,2 @@
+Fix a crash in free-threaded builds when :func:`codecs.unregister` is
+called concurrently.
diff --git a/Python/codecs.c b/Python/codecs.c
index 2621a17daf1814..ba6f618f77e6f6 100644
--- a/Python/codecs.c
+++ b/Python/codecs.c
@@ -64,24 +64,21 @@ PyCodec_Unregister(PyObject *search_function)
 
     PyObject *codec_search_path = interp->codecs.search_path;
     assert(PyList_CheckExact(codec_search_path));
+    FT_MUTEX_LOCK(&interp->codecs.search_path_mutex);
     for (Py_ssize_t i = 0; i < PyList_GET_SIZE(codec_search_path); i++) {
-        FT_MUTEX_LOCK(&interp->codecs.search_path_mutex);
-        PyObject *item = PyList_GetItemRef(codec_search_path, i);
-        int ret = 1;
-        if (item == search_function) {
-            // We hold a reference to the item, so its destructor can't run
-            // while we hold search_path_mutex.
-            ret = PyList_SetSlice(codec_search_path, i, i+1, NULL);
-        }
-        FT_MUTEX_UNLOCK(&interp->codecs.search_path_mutex);
-        Py_DECREF(item);
-        if (ret != 1) {
+        if (PyList_GET_ITEM(codec_search_path, i) == search_function) {
+            // Keep the function alive until after releasing the mutex.
+            Py_INCREF(search_function);
+            int ret = PyList_SetSlice(codec_search_path, i, i+1, NULL);
+            FT_MUTEX_UNLOCK(&interp->codecs.search_path_mutex);
+            Py_DECREF(search_function);
             assert(interp->codecs.search_cache != NULL);
             assert(PyDict_CheckExact(interp->codecs.search_cache));
             PyDict_Clear(interp->codecs.search_cache);
             return ret;
         }
     }
+    FT_MUTEX_UNLOCK(&interp->codecs.search_path_mutex);
     return 0;
 }
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to