https://github.com/python/cpython/commit/f2de1e6861c27bd498f598efc01600450979b5f9
commit: f2de1e6861c27bd498f598efc01600450979b5f9
branch: main
author: b-pass <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2025-05-18T20:32:29+05:30
summary:
gh-134144: Fix use-after-free in zapthreads() (#134145)
files:
A Misc/NEWS.d/next/C_API/2025-05-17-14-41-21.gh-issue-134144.xVpZik.rst
M Lib/test/test_interpreters/test_api.py
M Modules/_testinternalcapi.c
M Python/pystate.c
diff --git a/Lib/test/test_interpreters/test_api.py
b/Lib/test/test_interpreters/test_api.py
index 66c7afce88f0d8..1e2d572b1cbb81 100644
--- a/Lib/test/test_interpreters/test_api.py
+++ b/Lib/test/test_interpreters/test_api.py
@@ -1452,6 +1452,14 @@ def test_destroy(self):
self.assertFalse(
self.interp_exists(interpid))
+ with self.subTest('basic C-API'):
+ interpid = _testinternalcapi.create_interpreter()
+ self.assertTrue(
+ self.interp_exists(interpid))
+ _testinternalcapi.destroy_interpreter(interpid, basic=True)
+ self.assertFalse(
+ self.interp_exists(interpid))
+
def test_get_config(self):
# This test overlaps with
# test.test_capi.test_misc.InterpreterConfigTests.
diff --git
a/Misc/NEWS.d/next/C_API/2025-05-17-14-41-21.gh-issue-134144.xVpZik.rst
b/Misc/NEWS.d/next/C_API/2025-05-17-14-41-21.gh-issue-134144.xVpZik.rst
new file mode 100644
index 00000000000000..11c7bd59a4dd1d
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-05-17-14-41-21.gh-issue-134144.xVpZik.rst
@@ -0,0 +1 @@
+Fix crash when calling :c:func:`Py_EndInterpreter` with a :term:`thread state`
that isn't the initial thread for the interpreter.
diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c
index 92f744c5a5fc70..76bd76cc6b2490 100644
--- a/Modules/_testinternalcapi.c
+++ b/Modules/_testinternalcapi.c
@@ -1690,11 +1690,12 @@ create_interpreter(PyObject *self, PyObject *args,
PyObject *kwargs)
static PyObject *
destroy_interpreter(PyObject *self, PyObject *args, PyObject *kwargs)
{
- static char *kwlist[] = {"id", NULL};
+ static char *kwlist[] = {"id", "basic", NULL};
PyObject *idobj = NULL;
+ int basic = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
- "O:destroy_interpreter", kwlist,
- &idobj))
+ "O|p:destroy_interpreter", kwlist,
+ &idobj, &basic))
{
return NULL;
}
@@ -1704,7 +1705,27 @@ destroy_interpreter(PyObject *self, PyObject *args,
PyObject *kwargs)
return NULL;
}
- _PyXI_EndInterpreter(interp, NULL, NULL);
+ if (basic)
+ {
+ // Test the basic Py_EndInterpreter with weird out of order thread
states
+ PyThreadState *t1, *t2;
+ PyThreadState *prev;
+ t1 = interp->threads.head;
+ if (t1 == NULL) {
+ t1 = PyThreadState_New(interp);
+ }
+ t2 = PyThreadState_New(interp);
+ prev = PyThreadState_Swap(t2);
+ PyThreadState_Clear(t1);
+ PyThreadState_Delete(t1);
+ Py_EndInterpreter(t2);
+ PyThreadState_Swap(prev);
+ }
+ else
+ {
+ // use the cross interpreter _PyXI_EndInterpreter normally
+ _PyXI_EndInterpreter(interp, NULL, NULL);
+ }
Py_RETURN_NONE;
}
diff --git a/Python/pystate.c b/Python/pystate.c
index 1ac134400856d4..14ae2748b0bc99 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -1908,9 +1908,14 @@ tstate_delete_common(PyThreadState *tstate, int
release_gil)
static void
zapthreads(PyInterpreterState *interp)
{
+ PyThreadState *tstate;
/* No need to lock the mutex here because this should only happen
- when the threads are all really dead (XXX famous last words). */
- _Py_FOR_EACH_TSTATE_UNLOCKED(interp, tstate) {
+ when the threads are all really dead (XXX famous last words).
+
+ Cannot use _Py_FOR_EACH_TSTATE_UNLOCKED because we are freeing
+ the thread states here.
+ */
+ while ((tstate = interp->threads.head) != NULL) {
tstate_verify_not_active(tstate);
tstate_delete_common(tstate, 0);
free_threadstate((_PyThreadStateImpl *)tstate);
_______________________________________________
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]