[3.14] gh-134144: Fix use-after-free in zapthreads() (GH-134145) (#134182)

gh-134144: Fix use-after-free in zapthreads() (GH-134145)
(cherry picked from commit f2de1e6861)

Co-authored-by: b-pass <b-pass@users.noreply.github.com>
This commit is contained in:
Miss Islington (bot) 2025-05-18 17:29:19 +02:00 committed by GitHub
parent 8d51ed6b05
commit bb5f92adcf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 41 additions and 6 deletions

View file

@ -1452,6 +1452,14 @@ class LowLevelTests(TestBase):
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.

View file

@ -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.

View file

@ -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;
}

View file

@ -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);