From e7ab97862deb8ad69caccbc5c6e1f7b4e670afbd Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 29 Nov 2024 17:03:24 +0100 Subject: [PATCH] [3.12] gh-127208: Reject null character in _imp.create_dynamic() (#127400) (#127419) gh-127208: Reject null character in _imp.create_dynamic() (#127400) _imp.create_dynamic() now rejects embedded null characters in the path and in the module name. Backport also the _PyUnicode_AsUTF8NoNUL() function. (cherry picked from commit b14fdadc6c620875a20b7ccc3c9b069e85d8557a) --- Include/internal/pycore_unicodeobject.h | 3 +++ Lib/test/test_import/__init__.py | 13 +++++++++++++ Objects/unicodeobject.c | 12 ++++++++++++ Python/import.c | 8 +++++--- 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/Include/internal/pycore_unicodeobject.h b/Include/internal/pycore_unicodeobject.h index 44eccdea55beb0..cecdabe4155063 100644 --- a/Include/internal/pycore_unicodeobject.h +++ b/Include/internal/pycore_unicodeobject.h @@ -76,6 +76,9 @@ struct _Py_unicode_state { extern void _PyUnicode_ClearInterned(PyInterpreterState *interp); +// Like PyUnicode_AsUTF8(), but check for embedded null characters. +extern const char* _PyUnicode_AsUTF8NoNUL(PyObject *); + #ifdef __cplusplus } diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index c6accc4183a67f..81b2f33a840f1d 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -787,6 +787,19 @@ def test_issue105979(self): self.assertIn("Frozen object named 'x' is invalid", str(cm.exception)) + def test_create_dynamic_null(self): + with self.assertRaisesRegex(ValueError, 'embedded null character'): + class Spec: + name = "a\x00b" + origin = "abc" + _imp.create_dynamic(Spec()) + + with self.assertRaisesRegex(ValueError, 'embedded null character'): + class Spec2: + name = "abc" + origin = "a\x00b" + _imp.create_dynamic(Spec2()) + @skip_if_dont_write_bytecode class FilePermissionTests(unittest.TestCase): diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 7bd4b221c83cf7..5f3dd26ad7b762 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -3991,6 +3991,18 @@ PyUnicode_AsUTF8(PyObject *unicode) return PyUnicode_AsUTF8AndSize(unicode, NULL); } +const char * +_PyUnicode_AsUTF8NoNUL(PyObject *unicode) +{ + Py_ssize_t size; + const char *s = PyUnicode_AsUTF8AndSize(unicode, &size); + if (s && strlen(s) != (size_t)size) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + return NULL; + } + return s; +} + /* PyUnicode_GetSize() has been deprecated since Python 3.3 because it returned length of Py_UNICODE. diff --git a/Python/import.c b/Python/import.c index 4d0b7fd95569b3..66391c04a0baaa 100644 --- a/Python/import.c +++ b/Python/import.c @@ -917,12 +917,14 @@ extensions_lock_release(void) static void * hashtable_key_from_2_strings(PyObject *str1, PyObject *str2, const char sep) { - Py_ssize_t str1_len, str2_len; - const char *str1_data = PyUnicode_AsUTF8AndSize(str1, &str1_len); - const char *str2_data = PyUnicode_AsUTF8AndSize(str2, &str2_len); + const char *str1_data = _PyUnicode_AsUTF8NoNUL(str1); + const char *str2_data = _PyUnicode_AsUTF8NoNUL(str2); if (str1_data == NULL || str2_data == NULL) { return NULL; } + Py_ssize_t str1_len = strlen(str1_data); + Py_ssize_t str2_len = strlen(str2_data); + /* Make sure sep and the NULL byte won't cause an overflow. */ assert(SIZE_MAX - str1_len - str2_len > 2); size_t size = str1_len + 1 + str2_len + 1;