diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 773b97f631f2189..caeebecd99f660c 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -2062,6 +2062,24 @@ def test_open_code_hook(self): def test_audit(self): self.run_embedded_interpreter("test_audit") + def test_audit_hook_survives_allocator_change(self): + # A hook added before Py_Initialize() is freed at finalization, after + # PYTHONMALLOC changed the raw allocator: the entry must not be + # allocated with one allocator and freed with another. + allocators = ['debug'] + if not support.Py_GIL_DISABLED: + allocators.append('malloc_debug') + if support.with_mimalloc(): + allocators.extend(['mimalloc', 'mimalloc_debug']) + for allocator in allocators: + with self.subTest(allocator=allocator): + self.run_embedded_interpreter( + "test_audit", env={'PYTHONMALLOC': allocator}) + # The development mode installs the debug hooks too + with self.subTest(dev_mode=True): + self.run_embedded_interpreter( + "test_audit", env={'PYTHONDEVMODE': '1'}) + def test_audit_tuple(self): self.run_embedded_interpreter("test_audit_tuple") diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-11-05-10-51-17.gh-issue-161407.rVoqaQ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-11-05-10-51-17.gh-issue-161407.rVoqaQ.rst new file mode 100644 index 000000000000000..421329930f961a7 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-11-05-10-51-17.gh-issue-161407.rVoqaQ.rst @@ -0,0 +1,3 @@ +Fix a crash at exit when an audit hook was added with :c:func:`PySys_AddAuditHook` +before :c:func:`Py_Initialize` and the raw memory allocator changed during startup, for +example with ``PYTHONMALLOC=mimalloc`` or in the :ref:`Python Development Mode `. diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 6f336dac058379e..036c92ec8f7e41d 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -32,6 +32,7 @@ Data members: #include "pycore_pylifecycle.h" // _PyErr_WriteUnraisableDefaultHook() #include "pycore_pymath.h" // _PY_SHORT_FLOAT_REPR #include "pycore_pymem.h" // _PyMem_DefaultRawFree() +#include "pycore_pymem_init.h" // _PyMem_RawMalloc() #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_pystats.h" // _Py_PrintSpecializationStats() #include "pycore_runtime.h" // _PyRuntimeState_Get*() @@ -448,7 +449,7 @@ _PySys_ClearAuditHooks(PyThreadState *ts) runtime->audit_hooks.head = NULL; while (e) { n = e->next; - PyMem_RawFree(e); + _PyMem_RawFree(NULL, e); e = n; } } @@ -496,8 +497,12 @@ PySys_AddAuditHook(Py_AuditHookFunction hook, void *userData) } } - _Py_AuditHookEntry *e = (_Py_AuditHookEntry*)PyMem_RawMalloc( - sizeof(_Py_AuditHookEntry)); + /* Use malloc(), not PyMem_RawMalloc(): a hook can be added before + Py_Initialize(), and the entry is freed during finalization, after + PYTHONMALLOC may have changed the raw allocator (and, in debug builds, + the allocator under the debug hooks). */ + _Py_AuditHookEntry *e = (_Py_AuditHookEntry*)_PyMem_RawMalloc( + NULL, sizeof(_Py_AuditHookEntry)); if (!e) { if (tstate != NULL) { _PyErr_NoMemory(tstate);