From a7752968a1e865ba7ce05003679f9f468c26cf47 Mon Sep 17 00:00:00 2001 From: Shamil Date: Tue, 14 Oct 2025 17:42:17 +0300 Subject: [PATCH 1/3] gh-140067: Fix memory leak in sub-interpreter creation (#140111) Fix memory leak in sub-interpreter creation caused by overwriting of the previously used `_malloced` field. Now the pointer is stored in the first word of the memory block to avoid it being overwritten accidentally. Co-authored-by: Kumar Aditya --- Include/internal/pycore_interp_structs.h | 6 ------ ...2025-10-14-17-07-37.gh-issue-140067.ID2gOm.rst | 1 + Python/pystate.c | 15 +++++++++------ 3 files changed, 10 insertions(+), 12 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-10-14-17-07-37.gh-issue-140067.ID2gOm.rst diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index 2124e76514f1af..badc97808c6132 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -769,12 +769,6 @@ struct _is { * and should be placed at the beginning. */ struct _ceval_state ceval; - /* This structure is carefully allocated so that it's correctly aligned - * to avoid undefined behaviors during LOAD and STORE. The '_malloced' - * field stores the allocated pointer address that will later be freed. - */ - void *_malloced; - PyInterpreterState *next; int64_t id; diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-14-17-07-37.gh-issue-140067.ID2gOm.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-14-17-07-37.gh-issue-140067.ID2gOm.rst new file mode 100644 index 00000000000000..3c5a828101d9a8 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-14-17-07-37.gh-issue-140067.ID2gOm.rst @@ -0,0 +1 @@ +Fix memory leak in sub-interpreter creation. diff --git a/Python/pystate.c b/Python/pystate.c index dbed609f29aa07..bf6e4e56e6df87 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -457,16 +457,19 @@ _PyInterpreterState_Enable(_PyRuntimeState *runtime) static PyInterpreterState * alloc_interpreter(void) { + // Aligned allocation for PyInterpreterState. + // the first word of the memory block is used to store + // the original pointer to be used later to free the memory. size_t alignment = _Alignof(PyInterpreterState); - size_t allocsize = sizeof(PyInterpreterState) + alignment - 1; + size_t allocsize = sizeof(PyInterpreterState) + sizeof(void *) + alignment - 1; void *mem = PyMem_RawCalloc(1, allocsize); if (mem == NULL) { return NULL; } - PyInterpreterState *interp = _Py_ALIGN_UP(mem, alignment); - assert(_Py_IS_ALIGNED(interp, alignment)); - interp->_malloced = mem; - return interp; + void *ptr = _Py_ALIGN_UP((char *)mem + sizeof(void *), alignment); + ((void **)ptr)[-1] = mem; + assert(_Py_IS_ALIGNED(ptr, alignment)); + return ptr; } static void @@ -481,7 +484,7 @@ free_interpreter(PyInterpreterState *interp) interp->obmalloc = NULL; } assert(_Py_IS_ALIGNED(interp, _Alignof(PyInterpreterState))); - PyMem_RawFree(interp->_malloced); + PyMem_RawFree(((void **)interp)[-1]); } } From a1db21027979ee9add90038c4c2ed8cde9459b02 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Fri, 17 Oct 2025 22:05:03 +0530 Subject: [PATCH 2/3] add suppressions --- Tools/tsan/suppressions.txt | 3 +++ Tools/tsan/suppressions_free_threading.txt | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Tools/tsan/suppressions.txt b/Tools/tsan/suppressions.txt index 6bda5ecd570889..8f8910e23f9ca2 100644 --- a/Tools/tsan/suppressions.txt +++ b/Tools/tsan/suppressions.txt @@ -3,3 +3,6 @@ # https://gist.github.com/mpage/daaf32b39180c1989572957b943eb665 thread:pthread_create + +# gh-140138 +race:drop_gil \ No newline at end of file diff --git a/Tools/tsan/suppressions_free_threading.txt b/Tools/tsan/suppressions_free_threading.txt index 6bd31e8e6ecb9d..2877959dc27c71 100644 --- a/Tools/tsan/suppressions_free_threading.txt +++ b/Tools/tsan/suppressions_free_threading.txt @@ -45,3 +45,6 @@ race:PyObject_Realloc # gh-133467. Some of these could be hard to trigger. race_top:set_tp_bases race_top:type_set_bases_unlocked + +# gh-140138 +race:drop_gil \ No newline at end of file From d921b2c51899c12a33f48fce1ff96cb15f36f62c Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Fri, 17 Oct 2025 22:22:08 +0530 Subject: [PATCH 3/3] add skip marker --- Lib/test/test_threading.py | 1 + Tools/tsan/suppressions.txt | 3 --- Tools/tsan/suppressions_free_threading.txt | 3 --- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index d0f0e8ab2f7724..efd69a1f4fe468 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -1776,6 +1776,7 @@ def task(): self.assertEqual(os.read(r_interp, 1), DONE) @cpython_only + @support.skip_if_sanitizer(thread=True, memory=True) def test_daemon_threads_fatal_error(self): import_module("_testcapi") subinterp_code = f"""if 1: diff --git a/Tools/tsan/suppressions.txt b/Tools/tsan/suppressions.txt index 8f8910e23f9ca2..6bda5ecd570889 100644 --- a/Tools/tsan/suppressions.txt +++ b/Tools/tsan/suppressions.txt @@ -3,6 +3,3 @@ # https://gist.github.com/mpage/daaf32b39180c1989572957b943eb665 thread:pthread_create - -# gh-140138 -race:drop_gil \ No newline at end of file diff --git a/Tools/tsan/suppressions_free_threading.txt b/Tools/tsan/suppressions_free_threading.txt index 2877959dc27c71..6bd31e8e6ecb9d 100644 --- a/Tools/tsan/suppressions_free_threading.txt +++ b/Tools/tsan/suppressions_free_threading.txt @@ -45,6 +45,3 @@ race:PyObject_Realloc # gh-133467. Some of these could be hard to trigger. race_top:set_tp_bases race_top:type_set_bases_unlocked - -# gh-140138 -race:drop_gil \ No newline at end of file