Skip to content

Commit 58c44c2

Browse files
gh-140067: Fix memory leak in sub-interpreter creation (#140111) (#140261)
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 <[email protected]>
1 parent c8729c9 commit 58c44c2

File tree

4 files changed

+11
-12
lines changed

4 files changed

+11
-12
lines changed

Include/internal/pycore_interp_structs.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -769,12 +769,6 @@ struct _is {
769769
* and should be placed at the beginning. */
770770
struct _ceval_state ceval;
771771

772-
/* This structure is carefully allocated so that it's correctly aligned
773-
* to avoid undefined behaviors during LOAD and STORE. The '_malloced'
774-
* field stores the allocated pointer address that will later be freed.
775-
*/
776-
void *_malloced;
777-
778772
PyInterpreterState *next;
779773

780774
int64_t id;

Lib/test/test_threading.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1776,6 +1776,7 @@ def task():
17761776
self.assertEqual(os.read(r_interp, 1), DONE)
17771777

17781778
@cpython_only
1779+
@support.skip_if_sanitizer(thread=True, memory=True)
17791780
def test_daemon_threads_fatal_error(self):
17801781
import_module("_testcapi")
17811782
subinterp_code = f"""if 1:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix memory leak in sub-interpreter creation.

Python/pystate.c

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -457,16 +457,19 @@ _PyInterpreterState_Enable(_PyRuntimeState *runtime)
457457
static PyInterpreterState *
458458
alloc_interpreter(void)
459459
{
460+
// Aligned allocation for PyInterpreterState.
461+
// the first word of the memory block is used to store
462+
// the original pointer to be used later to free the memory.
460463
size_t alignment = _Alignof(PyInterpreterState);
461-
size_t allocsize = sizeof(PyInterpreterState) + alignment - 1;
464+
size_t allocsize = sizeof(PyInterpreterState) + sizeof(void *) + alignment - 1;
462465
void *mem = PyMem_RawCalloc(1, allocsize);
463466
if (mem == NULL) {
464467
return NULL;
465468
}
466-
PyInterpreterState *interp = _Py_ALIGN_UP(mem, alignment);
467-
assert(_Py_IS_ALIGNED(interp, alignment));
468-
interp->_malloced = mem;
469-
return interp;
469+
void *ptr = _Py_ALIGN_UP((char *)mem + sizeof(void *), alignment);
470+
((void **)ptr)[-1] = mem;
471+
assert(_Py_IS_ALIGNED(ptr, alignment));
472+
return ptr;
470473
}
471474

472475
static void
@@ -481,7 +484,7 @@ free_interpreter(PyInterpreterState *interp)
481484
interp->obmalloc = NULL;
482485
}
483486
assert(_Py_IS_ALIGNED(interp, _Alignof(PyInterpreterState)));
484-
PyMem_RawFree(interp->_malloced);
487+
PyMem_RawFree(((void **)interp)[-1]);
485488
}
486489
}
487490

0 commit comments

Comments
 (0)