https://github.com/python/cpython/commit/a2e551610f6248cfd130172e1cf9f5ee811758b9
commit: a2e551610f6248cfd130172e1cf9f5ee811758b9
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: ZeroIntensity <[email protected]>
date: 2026-06-12T01:47:01Z
summary:

[3.15] gh-151297: Fix undefined behavior in `_PyObject_MiRealloc` (GH-151358) 
(GH-151388)

The standard says that a call to `memcpy` must pass a valid source and
destination pointer even if the size is 0, so we must avoid calling
`memcpy` when our source pointer is NULL. If we don't, an optimizing
compiler can decide that the pointer must be non-NULL based on the
presence of UB, and optimize out checks for null pointers.

Specifically, note that the standard says:

    Where an argument declared as size_t n specifies the length of the
    array for a function, n can have the value zero on a call to that
    function. Unless explicitly stated otherwise in the description of
    a particular function in this subclause, pointer arguments on such
    a call shall still have valid values, as described in 7.1.4.

And section 7.1.4 says:

    If an argument to a function has an invalid value (such as a value
    outside the domain of the function, or a pointer outside the address
    space of the program, or a null pointer, or a pointer to
    non-modifiable storage when the corresponding parameter is not
    const-qualified) or a type (after default argument promotion) not
    expected by a function with a variable number of arguments, the
    behavior is undefined.

The specification for `memcpy` doesn't state that it's allowed to be
called with null pointers, and Linux's `/usr/include/string.h` declares
`memcpy` as `__nonnull ((1, 2))`.
(cherry picked from commit c37599200f688538efa34a49f262a9a4a899a953)

Co-authored-by: Matt Wozniski <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-06-11-16-03-23.gh-issue-151297.NGPkUM.rst
M Modules/_testcapi/mem.c
M Objects/obmalloc.c

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 000000000000000..288d726e0f1004d
--- /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 
<free-threaded build>` or with :envvar:`PYTHONMALLOC` set to ``mimalloc``.
diff --git a/Modules/_testcapi/mem.c b/Modules/_testcapi/mem.c
index b4896f984510bd6..6aaf0c7d4e942d3 100644
--- a/Modules/_testcapi/mem.c
+++ b/Modules/_testcapi/mem.c
@@ -323,6 +323,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 1809bd30451327b..0947d47c8a55582 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;

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to