Skip to content

Commit 784b305

Browse files
authored
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.
1 parent 4a61673 commit 784b305

2 files changed

Lines changed: 10 additions & 11 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a crash in free-threaded builds when :func:`codecs.unregister` is
2+
called concurrently.

‎Python/codecs.c‎

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,24 +64,21 @@ PyCodec_Unregister(PyObject *search_function)
6464

6565
PyObject *codec_search_path = interp->codecs.search_path;
6666
assert(PyList_CheckExact(codec_search_path));
67+
FT_MUTEX_LOCK(&interp->codecs.search_path_mutex);
6768
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(codec_search_path); i++) {
68-
FT_MUTEX_LOCK(&interp->codecs.search_path_mutex);
69-
PyObject *item = PyList_GetItemRef(codec_search_path, i);
70-
int ret = 1;
71-
if (item == search_function) {
72-
// We hold a reference to the item, so its destructor can't run
73-
// while we hold search_path_mutex.
74-
ret = PyList_SetSlice(codec_search_path, i, i+1, NULL);
75-
}
76-
FT_MUTEX_UNLOCK(&interp->codecs.search_path_mutex);
77-
Py_DECREF(item);
78-
if (ret != 1) {
69+
if (PyList_GET_ITEM(codec_search_path, i) == search_function) {
70+
// Keep the function alive until after releasing the mutex.
71+
Py_INCREF(search_function);
72+
int ret = PyList_SetSlice(codec_search_path, i, i+1, NULL);
73+
FT_MUTEX_UNLOCK(&interp->codecs.search_path_mutex);
74+
Py_DECREF(search_function);
7975
assert(interp->codecs.search_cache != NULL);
8076
assert(PyDict_CheckExact(interp->codecs.search_cache));
8177
PyDict_Clear(interp->codecs.search_cache);
8278
return ret;
8379
}
8480
}
81+
FT_MUTEX_UNLOCK(&interp->codecs.search_path_mutex);
8582
return 0;
8683
}
8784

0 commit comments

Comments
 (0)