https://github.com/python/cpython/commit/ec4df5913404e380705283f963c8cdd8c6bab438
commit: ec4df5913404e380705283f963c8cdd8c6bab438
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-05-25T12:16:08+05:30
summary:

[3.14] gh-149449: Fix use-after-free in `_PyUnicode_GetNameCAPI` (GH-150323) 
(#150353)

gh-149449: Fix use-after-free in `_PyUnicode_GetNameCAPI` (GH-150323)
(cherry picked from commit 43c60ec2fddd316a4a6b7b6c68eae7cb66df0850)

Co-authored-by: Pieter Eendebak <[email protected]>
Co-authored-by: Kumar Aditya <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-05-23-22-08-01.gh-issue-149449.2lhQFF.rst
M Lib/test/test_unicodedata.py
M Modules/unicodedata.c
M Tools/c-analyzer/cpython/ignored.tsv

diff --git a/Lib/test/test_unicodedata.py b/Lib/test/test_unicodedata.py
index 93d573996407590..a27496a1d8384b4 100644
--- a/Lib/test/test_unicodedata.py
+++ b/Lib/test/test_unicodedata.py
@@ -685,6 +685,22 @@ def test_failed_import_during_compiling(self):
             "(can't load unicodedata module)"
         self.assertIn(error, result.err.decode("ascii"))
 
+    def test_unicodedata_unload_reload(self):
+        # gh-149449: dropping unicodedata and running gc must not leave the
+        # cached _ucnhash_CAPI pointer dangling.
+        code = (
+            "import gc, sys\n"
+            "assert '\\N{GRINNING FACE}'.encode("
+            "    'ascii', errors='namereplace') == b'\\\\N{GRINNING FACE}'\n"
+            "compile(r\"x = '\\\\N{LATIN CAPITAL LETTER A}'\", '<x>', 
'exec')\n"
+            "del sys.modules['unicodedata']\n"
+            "gc.collect()\n"
+            "assert '\\N{WINKING FACE}'.encode("
+            "    'ascii', errors='namereplace') == b'\\\\N{WINKING FACE}'\n"
+            "compile(r\"x = '\\\\N{LATIN CAPITAL LETTER B}'\", '<x>', 
'exec')\n"
+        )
+        script_helper.assert_python_ok("-c", code)
+
     def test_decimal_numeric_consistent(self):
         # Test that decimal and numeric are consistent,
         # i.e. if a character has a decimal value,
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-23-22-08-01.gh-issue-149449.2lhQFF.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-23-22-08-01.gh-issue-149449.2lhQFF.rst
new file mode 100644
index 000000000000000..7d11442468d2077
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-23-22-08-01.gh-issue-149449.2lhQFF.rst
@@ -0,0 +1,3 @@
+Fix a use-after-free crash when the :mod:`unicodedata` module was removed
+from :data:`sys.modules` and garbage-collected between calls that decode
+``\N{...}`` escapes or use the ``namereplace`` codec error handler.
diff --git a/Modules/unicodedata.c b/Modules/unicodedata.c
index 83de1be56a7fafc..1c2e7896bdbadf0 100644
--- a/Modules/unicodedata.c
+++ b/Modules/unicodedata.c
@@ -1503,32 +1503,17 @@ capi_getcode(const char* name, int namelen, Py_UCS4* 
code,
     return _check_alias_and_seq(code, with_named_seq);
 }
 
-static void
-unicodedata_destroy_capi(PyObject *capsule)
-{
-    void *capi = PyCapsule_GetPointer(capsule, PyUnicodeData_CAPSULE_NAME);
-    PyMem_Free(capi);
-}
-
 static PyObject *
 unicodedata_create_capi(void)
 {
-    _PyUnicode_Name_CAPI *capi = PyMem_Malloc(sizeof(_PyUnicode_Name_CAPI));
-    if (capi == NULL) {
-        PyErr_NoMemory();
-        return NULL;
-    }
-    capi->getname = capi_getucname;
-    capi->getcode = capi_getcode;
-
-    PyObject *capsule = PyCapsule_New(capi,
-                                      PyUnicodeData_CAPSULE_NAME,
-                                      unicodedata_destroy_capi);
-    if (capsule == NULL) {
-        PyMem_Free(capi);
-    }
-    return capsule;
-};
+    // Statically allocated so that any cached pointers stay valid after 
unicodedata
+    // is removed from sys.modules and the capsule is gc'd (gh-149449).
+    static _PyUnicode_Name_CAPI capi = {
+        .getname = capi_getucname,
+        .getcode = capi_getcode,
+    };
+    return PyCapsule_New(&capi, PyUnicodeData_CAPSULE_NAME, NULL);
+}
 
 
 /* -------------------------------------------------------------------- */
diff --git a/Tools/c-analyzer/cpython/ignored.tsv 
b/Tools/c-analyzer/cpython/ignored.tsv
index 737924fd8581c01..57fc14400d38a92 100644
--- a/Tools/c-analyzer/cpython/ignored.tsv
+++ b/Tools/c-analyzer/cpython/ignored.tsv
@@ -323,6 +323,7 @@ Modules/pyexpat.c   -       error_info_of   -
 Modules/pyexpat.c      -       handler_info    -
 Modules/termios.c      -       termios_constants       -
 Modules/timemodule.c   init_timezone   YEAR    -
+Modules/unicodedata.c  unicodedata_create_capi capi    -
 Objects/bytearrayobject.c      -       _PyByteArray_empty_string       -
 Objects/complexobject.c        -       c_1     -
 Objects/exceptions.c   -       static_exceptions       -

_______________________________________________
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