]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-149449: Fix use-after-free in `_PyUnicode_GetNameCAPI` (#150323)
authorPieter Eendebak <pieter.eendebak@gmail.com>
Sun, 24 May 2026 16:17:38 +0000 (18:17 +0200)
committerGitHub <noreply@github.com>
Sun, 24 May 2026 16:17:38 +0000 (16:17 +0000)
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
Lib/test/test_unicodedata.py
Misc/NEWS.d/next/Core_and_Builtins/2026-05-23-22-08-01.gh-issue-149449.2lhQFF.rst [new file with mode: 0644]
Modules/unicodedata.c
Tools/c-analyzer/cpython/ignored.tsv

index 8ecb0df2f8e5ddce5999efd44d984a5287653f97..060d81415aa1f1b61c0877f28afbb5df3fd09cc8 100644 (file)
@@ -1106,6 +1106,22 @@ class UnicodeMiscTest(unittest.TestCase):
             "(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 (file)
index 0000000..7d11442
--- /dev/null
@@ -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.
index 31ad916b2c5c26a1d6b53a25028a208b0090fbcb..6bb25fc0b63781c08d036bf93078505ae08e8b85 100644 (file)
@@ -1548,32 +1548,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);
+}
 
 
 /* -------------------------------------------------------------------- */
index ddfb93a424c0185f613f709c6e9b9f4f4f8ebb57..bf08e5568205e7a03471e4aee75062d3b923b69a 100644 (file)
@@ -327,6 +327,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       -