diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-11-16-03-23.gh-issue-151297.NGPkUM.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-11-16-03-23.gh-issue-151297.NGPkUM.rst new file mode 100644 index 00000000000000..288d726e0f1004 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-11-16-03-23.gh-issue-151297.NGPkUM.rst @@ -0,0 +1 @@ +Fix an invalid pointer dereference that could occur when calling :c:func:`PyObject_Realloc` with a NULL pointer in :term:`free-threaded builds ` or with :envvar:`PYTHONMALLOC` set to ``mimalloc``. diff --git a/Modules/_testcapi/mem.c b/Modules/_testcapi/mem.c index 7909476ac11aa6..28e89275d61705 100644 --- a/Modules/_testcapi/mem.c +++ b/Modules/_testcapi/mem.c @@ -345,6 +345,53 @@ test_setallocators(PyMemAllocatorDomain domain) goto fail; } + /* realloc(NULL, size) should behave like malloc(size) */ + size_t size3 = 100; + void *ptr3; + switch(domain) { + case PYMEM_DOMAIN_RAW: + ptr3 = PyMem_RawRealloc(NULL, size3); + break; + case PYMEM_DOMAIN_MEM: + ptr3 = PyMem_Realloc(NULL, size3); + break; + case PYMEM_DOMAIN_OBJ: + ptr3 = PyObject_Realloc(NULL, size3); + break; + default: + ptr3 = NULL; + break; + } + + CHECK_CTX("realloc(NULL, size)"); + if (ptr3 == NULL) { + error_msg = "realloc(NULL, size) failed"; + goto fail; + } + if (hook.realloc_ptr != NULL || hook.realloc_new_size != size3) { + error_msg = "realloc(NULL, size) invalid parameters"; + goto fail; + } + + hook.free_ptr = NULL; + switch(domain) { + case PYMEM_DOMAIN_RAW: + PyMem_RawFree(ptr3); + break; + case PYMEM_DOMAIN_MEM: + PyMem_Free(ptr3); + break; + case PYMEM_DOMAIN_OBJ: + PyObject_Free(ptr3); + break; + } + + CHECK_CTX("realloc(NULL, size) free"); + if (hook.free_ptr != ptr3) { + error_msg = "unexpected pointer passed to free"; + goto fail; + } + res = Py_NewRef(Py_None); goto finally; diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index 1809bd30451327..0947d47c8a5558 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -363,7 +363,10 @@ _PyObject_MiRealloc(void *ctx, void *ptr, size_t nbytes) _mi_memcpy((char*)newp + offset, (char*)ptr + offset, copy_size - offset); } else { - _mi_memcpy(newp, ptr, copy_size); + // memcpy(dst, NULL, 0) is undefined behavior. See gh-151297. + if mi_likely(ptr) { + _mi_memcpy(newp, ptr, copy_size); + } } mi_free(ptr); return newp;