Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Lib/test/test_unicodedata.py
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
31 changes: 8 additions & 23 deletions Modules/unicodedata.c
Original file line number Diff line number Diff line change
Expand Up @@ -1543,32 +1543,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);
}


/* -------------------------------------------------------------------- */
Expand Down
1 change: 1 addition & 0 deletions Tools/c-analyzer/cpython/ignored.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -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 -
Expand Down
Loading