[3.14] gh-140067: Fix memory leak in sub-interpreter creation (GH-140111) (#140118)

* [3.14] gh-140067: Fix memory leak in sub-interpreter creation  (GH-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.
(cherry picked from commit 59547a251f)

Co-authored-by: Shamil <ashm.tech@proton.me>
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
This commit is contained in:
Kumar Aditya 2025-10-18 19:40:43 +05:30 committed by GitHub
parent 3ca7ea1f8f
commit 1d11627ba5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 12 additions and 10 deletions

View file

@ -768,10 +768,7 @@ struct _is {
* and should be placed at the beginning. */ * and should be placed at the beginning. */
struct _ceval_state ceval; struct _ceval_state ceval;
/* This structure is carefully allocated so that it's correctly aligned // unused, kept for ABI compatibility
* to avoid undefined behaviors during LOAD and STORE. The '_malloced'
* field stores the allocated pointer address that will later be freed.
*/
void *_malloced; void *_malloced;
PyInterpreterState *next; PyInterpreterState *next;

View file

@ -1727,6 +1727,7 @@ def task():
self.assertEqual(os.read(r_interp, 1), DONE) self.assertEqual(os.read(r_interp, 1), DONE)
@cpython_only @cpython_only
@support.skip_if_sanitizer(thread=True, memory=True)
def test_daemon_threads_fatal_error(self): def test_daemon_threads_fatal_error(self):
import_module("_testcapi") import_module("_testcapi")
subinterp_code = f"""if 1: subinterp_code = f"""if 1:

View file

@ -0,0 +1 @@
Fix memory leak in sub-interpreter creation.

View file

@ -565,16 +565,19 @@ _PyInterpreterState_Enable(_PyRuntimeState *runtime)
static PyInterpreterState * static PyInterpreterState *
alloc_interpreter(void) 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 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); void *mem = PyMem_RawCalloc(1, allocsize);
if (mem == NULL) { if (mem == NULL) {
return NULL; return NULL;
} }
PyInterpreterState *interp = _Py_ALIGN_UP(mem, alignment); void *ptr = _Py_ALIGN_UP((char *)mem + sizeof(void *), alignment);
assert(_Py_IS_ALIGNED(interp, alignment)); ((void **)ptr)[-1] = mem;
interp->_malloced = mem; assert(_Py_IS_ALIGNED(ptr, alignment));
return interp; return ptr;
} }
static void static void
@ -589,7 +592,7 @@ free_interpreter(PyInterpreterState *interp)
interp->obmalloc = NULL; interp->obmalloc = NULL;
} }
assert(_Py_IS_ALIGNED(interp, _Alignof(PyInterpreterState))); assert(_Py_IS_ALIGNED(interp, _Alignof(PyInterpreterState)));
PyMem_RawFree(interp->_malloced); PyMem_RawFree(((void **)interp)[-1]);
} }
} }