Skip to content

Commit ec4df59

Browse files
miss-islingtoneendebakptkumaraditya303
authored
[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 43c60ec) Co-authored-by: Pieter Eendebak <pieter.eendebak@gmail.com> Co-authored-by: Kumar Aditya <kumaraditya@python.org>
1 parent 229447a commit ec4df59

4 files changed

Lines changed: 28 additions & 23 deletions

File tree

Lib/test/test_unicodedata.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,22 @@ def test_failed_import_during_compiling(self):
685685
"(can't load unicodedata module)"
686686
self.assertIn(error, result.err.decode("ascii"))
687687

688+
def test_unicodedata_unload_reload(self):
689+
# gh-149449: dropping unicodedata and running gc must not leave the
690+
# cached _ucnhash_CAPI pointer dangling.
691+
code = (
692+
"import gc, sys\n"
693+
"assert '\\N{GRINNING FACE}'.encode("
694+
" 'ascii', errors='namereplace') == b'\\\\N{GRINNING FACE}'\n"
695+
"compile(r\"x = '\\\\N{LATIN CAPITAL LETTER A}'\", '<x>', 'exec')\n"
696+
"del sys.modules['unicodedata']\n"
697+
"gc.collect()\n"
698+
"assert '\\N{WINKING FACE}'.encode("
699+
" 'ascii', errors='namereplace') == b'\\\\N{WINKING FACE}'\n"
700+
"compile(r\"x = '\\\\N{LATIN CAPITAL LETTER B}'\", '<x>', 'exec')\n"
701+
)
702+
script_helper.assert_python_ok("-c", code)
703+
688704
def test_decimal_numeric_consistent(self):
689705
# Test that decimal and numeric are consistent,
690706
# i.e. if a character has a decimal value,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a use-after-free crash when the :mod:`unicodedata` module was removed
2+
from :data:`sys.modules` and garbage-collected between calls that decode
3+
``\N{...}`` escapes or use the ``namereplace`` codec error handler.

Modules/unicodedata.c

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,32 +1503,17 @@ capi_getcode(const char* name, int namelen, Py_UCS4* code,
15031503
return _check_alias_and_seq(code, with_named_seq);
15041504
}
15051505

1506-
static void
1507-
unicodedata_destroy_capi(PyObject *capsule)
1508-
{
1509-
void *capi = PyCapsule_GetPointer(capsule, PyUnicodeData_CAPSULE_NAME);
1510-
PyMem_Free(capi);
1511-
}
1512-
15131506
static PyObject *
15141507
unicodedata_create_capi(void)
15151508
{
1516-
_PyUnicode_Name_CAPI *capi = PyMem_Malloc(sizeof(_PyUnicode_Name_CAPI));
1517-
if (capi == NULL) {
1518-
PyErr_NoMemory();
1519-
return NULL;
1520-
}
1521-
capi->getname = capi_getucname;
1522-
capi->getcode = capi_getcode;
1523-
1524-
PyObject *capsule = PyCapsule_New(capi,
1525-
PyUnicodeData_CAPSULE_NAME,
1526-
unicodedata_destroy_capi);
1527-
if (capsule == NULL) {
1528-
PyMem_Free(capi);
1529-
}
1530-
return capsule;
1531-
};
1509+
// Statically allocated so that any cached pointers stay valid after unicodedata
1510+
// is removed from sys.modules and the capsule is gc'd (gh-149449).
1511+
static _PyUnicode_Name_CAPI capi = {
1512+
.getname = capi_getucname,
1513+
.getcode = capi_getcode,
1514+
};
1515+
return PyCapsule_New(&capi, PyUnicodeData_CAPSULE_NAME, NULL);
1516+
}
15321517

15331518

15341519
/* -------------------------------------------------------------------- */

Tools/c-analyzer/cpython/ignored.tsv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ Modules/pyexpat.c - error_info_of -
323323
Modules/pyexpat.c - handler_info -
324324
Modules/termios.c - termios_constants -
325325
Modules/timemodule.c init_timezone YEAR -
326+
Modules/unicodedata.c unicodedata_create_capi capi -
326327
Objects/bytearrayobject.c - _PyByteArray_empty_string -
327328
Objects/complexobject.c - c_1 -
328329
Objects/exceptions.c - static_exceptions -

0 commit comments

Comments
 (0)