From ad524375af042a549d28ec252f3071a595b892b2 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 16 Mar 2016 12:12:53 +0100 Subject: [PATCH] Fail if PyMem_Malloc() is called without holding the GIL Issue #26563: Debug hooks on Python memory allocators now raise a fatal error if functions of the PyMem_Malloc() family are called without holding the GIL. --- Lib/test/test_capi.py | 17 +++++++++++++---- Misc/NEWS | 4 ++++ Modules/_testcapimodule.c | 19 +++++++++++++++++++ Objects/obmalloc.c | 14 +++++++------- 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index 8e6245bf6f8..8f4836a7cec 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -602,15 +602,24 @@ class PyMemDebugTests(unittest.TestCase): regex = regex.format(ptr=self.PTR_REGEX) self.assertRegex(out, regex) - def test_pyobject_malloc_without_gil(self): - # Calling PyObject_Malloc() without holding the GIL must raise an - # error in debug mode. - code = 'import _testcapi; _testcapi.pyobject_malloc_without_gil()' + def check_malloc_without_gil(self, code): out = self.check(code) expected = ('Fatal Python error: Python memory allocator called ' 'without holding the GIL') self.assertIn(expected, out) + def test_pymem_malloc_without_gil(self): + # Debug hooks must raise an error if PyMem_Malloc() is called + # without holding the GIL + code = 'import _testcapi; _testcapi.pymem_malloc_without_gil()' + self.check_malloc_without_gil(code) + + def test_pyobject_malloc_without_gil(self): + # Debug hooks must raise an error if PyObject_Malloc() is called + # without holding the GIL + code = 'import _testcapi; _testcapi.pyobject_malloc_without_gil()' + self.check_malloc_without_gil(code) + class PyMemMallocDebugTests(PyMemDebugTests): PYTHONMALLOC = 'malloc_debug' diff --git a/Misc/NEWS b/Misc/NEWS index 7f93b2bab4c..adfa04bba5f 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,10 @@ Release date: tba Core and Builtins ----------------- +- Issue #26563: Debug hooks on Python memory allocators now raise a fatal + error if functions of the :c:func:`PyMem_Malloc` family are called without + holding the GIL. + - Issue #26564: On error, the debug hooks on Python memory allocators now use the :mod:`tracemalloc` module to get the traceback where a memory block was allocated. diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index b3d8818ac67..0fc7cbc3288 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3643,11 +3643,29 @@ pymem_api_misuse(PyObject *self, PyObject *args) Py_RETURN_NONE; } +static PyObject* +pymem_malloc_without_gil(PyObject *self, PyObject *args) +{ + char *buffer; + + /* Deliberate bug to test debug hooks on Python memory allocators: + call PyMem_Malloc() without holding the GIL */ + Py_BEGIN_ALLOW_THREADS + buffer = PyMem_Malloc(10); + Py_END_ALLOW_THREADS + + PyMem_Free(buffer); + + Py_RETURN_NONE; +} + static PyObject* pyobject_malloc_without_gil(PyObject *self, PyObject *args) { char *buffer; + /* Deliberate bug to test debug hooks on Python memory allocators: + call PyObject_Malloc() without holding the GIL */ Py_BEGIN_ALLOW_THREADS buffer = PyObject_Malloc(10); Py_END_ALLOW_THREADS @@ -3841,6 +3859,7 @@ static PyMethodDef TestMethods[] = { {"get_recursion_depth", get_recursion_depth, METH_NOARGS}, {"pymem_buffer_overflow", pymem_buffer_overflow, METH_NOARGS}, {"pymem_api_misuse", pymem_api_misuse, METH_NOARGS}, + {"pymem_malloc_without_gil", pymem_malloc_without_gil, METH_NOARGS}, {"pyobject_malloc_without_gil", pyobject_malloc_without_gil, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index 8812f592b6c..503fcdfc5a5 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -198,7 +198,7 @@ static PyMemAllocatorEx _PyMem_Raw = { static PyMemAllocatorEx _PyMem = { #ifdef Py_DEBUG - &_PyMem_Debug.mem, PYRAWDBG_FUNCS + &_PyMem_Debug.mem, PYDBG_FUNCS #else NULL, PYMEM_FUNCS #endif @@ -321,17 +321,17 @@ PyMem_SetupDebugHooks(void) PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &alloc); } - if (_PyMem.malloc != _PyMem_DebugRawMalloc) { - alloc.ctx = &_PyMem_Debug.mem; - PyMem_GetAllocator(PYMEM_DOMAIN_MEM, &_PyMem_Debug.mem.alloc); - PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &alloc); - } - alloc.malloc = _PyMem_DebugMalloc; alloc.calloc = _PyMem_DebugCalloc; alloc.realloc = _PyMem_DebugRealloc; alloc.free = _PyMem_DebugFree; + if (_PyMem.malloc != _PyMem_DebugMalloc) { + alloc.ctx = &_PyMem_Debug.mem; + PyMem_GetAllocator(PYMEM_DOMAIN_MEM, &_PyMem_Debug.mem.alloc); + PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &alloc); + } + if (_PyObject.malloc != _PyMem_DebugMalloc) { alloc.ctx = &_PyMem_Debug.obj; PyMem_GetAllocator(PYMEM_DOMAIN_OBJ, &_PyMem_Debug.obj.alloc);