]> granicus.if.org Git - python/commitdiff
[3.6] bpo-13617: Reject embedded null characters in wchar* strings. (GH-2302) (#2462)
authorSerhiy Storchaka <storchaka@gmail.com>
Wed, 28 Jun 2017 06:27:35 +0000 (09:27 +0300)
committerGitHub <noreply@github.com>
Wed, 28 Jun 2017 06:27:35 +0000 (09:27 +0300)
Based on patch by Victor Stinner.

Add private C API function _PyUnicode_AsUnicode() which is similar to
PyUnicode_AsUnicode(), but checks for null characters..
(cherry picked from commit f7eae0adfcd4c50034281b2c69f461b43b68db84)

22 files changed:
Include/unicodeobject.h
Lib/ctypes/test/test_loading.py
Lib/test/test_builtin.py
Lib/test/test_curses.py
Lib/test/test_grp.py
Lib/test/test_imp.py
Lib/test/test_locale.py
Lib/test/test_time.py
Lib/test/test_winsound.py
Modules/_ctypes/callproc.c
Modules/_cursesmodule.c
Modules/_io/fileio.c
Modules/_localemodule.c
Modules/grpmodule.c
Modules/nismodule.c
Modules/posixmodule.c
Modules/pwdmodule.c
Modules/spwdmodule.c
Objects/unicodeobject.c
PC/_msi.c
Python/dynload_win.c
Python/fileutils.c

index cec2b7f4b07e7184594dbcb70cb48ba3f2f5b2f5..f498873870082fda3208b0762f35731e76e0099b 100644 (file)
@@ -752,23 +752,27 @@ PyAPI_FUNC(Py_UCS4*) PyUnicode_AsUCS4(
 PyAPI_FUNC(Py_UCS4*) PyUnicode_AsUCS4Copy(PyObject *unicode);
 #endif
 
+#ifndef Py_LIMITED_API
 /* Return a read-only pointer to the Unicode object's internal
    Py_UNICODE buffer.
    If the wchar_t/Py_UNICODE representation is not yet available, this
    function will calculate it. */
 
-#ifndef Py_LIMITED_API
 PyAPI_FUNC(Py_UNICODE *) PyUnicode_AsUnicode(
     PyObject *unicode           /* Unicode object */
     );
-#endif
+
+/* Similar to PyUnicode_AsUnicode(), but raises a ValueError if the string
+   contains null characters. */
+PyAPI_FUNC(const Py_UNICODE *) _PyUnicode_AsUnicode(
+    PyObject *unicode           /* Unicode object */
+    );
 
 /* Return a read-only pointer to the Unicode object's internal
    Py_UNICODE buffer and save the length at size.
    If the wchar_t/Py_UNICODE representation is not yet available, this
    function will calculate it. */
 
-#ifndef Py_LIMITED_API
 PyAPI_FUNC(Py_UNICODE *) PyUnicode_AsUnicodeAndSize(
     PyObject *unicode,          /* Unicode object */
     Py_ssize_t *size            /* location where to save the length */
index 45571f3d3d5209baf7b15b8cab76fcd8f11fe67d..f3b65b9d6e7e2e46fcc8b4dc6e5179f69c14e5e8 100644 (file)
@@ -62,6 +62,8 @@ class LoaderTest(unittest.TestCase):
             windll["kernel32"].GetModuleHandleW
             windll.LoadLibrary("kernel32").GetModuleHandleW
             WinDLL("kernel32").GetModuleHandleW
+            # embedded null character
+            self.assertRaises(ValueError, windll.LoadLibrary, "kernel32\0")
 
     @unittest.skipUnless(os.name == "nt",
                          'test specific to Windows')
index 416316c02852c82ef3b156ea413f1da16648d17d..7a4b7eb9176ab93251cdbab91b34329c12f60e41 100644 (file)
@@ -151,6 +151,8 @@ class BuiltinTest(unittest.TestCase):
         self.assertRaises(TypeError, __import__, 1, 2, 3, 4)
         self.assertRaises(ValueError, __import__, '')
         self.assertRaises(TypeError, __import__, 'sys', name='sys')
+        # embedded null character
+        self.assertRaises(ModuleNotFoundError, __import__, 'string\x00')
 
     def test_abs(self):
         # int
@@ -1002,6 +1004,10 @@ class BuiltinTest(unittest.TestCase):
             self.assertEqual(fp.read(300), 'XXX'*100)
             self.assertEqual(fp.read(1000), 'YYY'*100)
 
+        # embedded null bytes and characters
+        self.assertRaises(ValueError, open, 'a\x00b')
+        self.assertRaises(ValueError, open, b'a\x00b')
+
     def test_open_default_encoding(self):
         old_environ = dict(os.environ)
         try:
index 3d8c50bcb6c6ae4aedd1a1159481ac3dc2002c66..0d0b160d7645a55a1b1f3f90e4c23c4255b2cc30 100644 (file)
@@ -81,7 +81,7 @@ class TestCurses(unittest.TestCase):
         win2 = curses.newwin(15,15, 5,5)
 
         for meth in [stdscr.addch, stdscr.addstr]:
-            for args in [('a'), ('a', curses.A_BOLD),
+            for args in [('a',), ('a', curses.A_BOLD),
                          (4,4, 'a'), (5,5, 'a', curses.A_BOLD)]:
                 with self.subTest(meth=meth.__qualname__, args=args):
                     meth(*args)
@@ -194,6 +194,15 @@ class TestCurses(unittest.TestCase):
         self.assertRaises(ValueError, stdscr.instr, -2)
         self.assertRaises(ValueError, stdscr.instr, 2, 3, -2)
 
+    def test_embedded_null_chars(self):
+        # reject embedded null bytes and characters
+        stdscr = self.stdscr
+        for arg in ['a', b'a']:
+            with self.subTest(arg=arg):
+                self.assertRaises(ValueError, stdscr.addstr, 'a\0')
+                self.assertRaises(ValueError, stdscr.addnstr, 'a\0', 1)
+                self.assertRaises(ValueError, stdscr.insstr, 'a\0')
+                self.assertRaises(ValueError, stdscr.insnstr, 'a\0', 1)
 
     def test_module_funcs(self):
         "Test module-level functions"
index 69095a3fb98d743347e67182366ecd904cabb726..e511947858c0a32ab9a27e9416dd2d9eedaa1e86 100644 (file)
@@ -50,6 +50,8 @@ class GroupDatabaseTestCase(unittest.TestCase):
         self.assertRaises(TypeError, grp.getgrgid)
         self.assertRaises(TypeError, grp.getgrnam)
         self.assertRaises(TypeError, grp.getgrall, 42)
+        # embedded null character
+        self.assertRaises(ValueError, grp.getgrnam, 'a\x00b')
 
         # try to get some errors
         bynames = {}
index 4ece3654108e4966b3eb3c1d390963fe65d90232..6f35f49487ce7f979f00d255f022b8dba32cb9af 100644 (file)
@@ -314,6 +314,10 @@ class ImportTests(unittest.TestCase):
         loader.get_data(imp.__file__)  # File should be closed
         loader.get_data(imp.__file__)  # Will need to create a newly opened file
 
+    def test_load_source(self):
+        with self.assertRaisesRegex(ValueError, 'embedded null'):
+            imp.load_source(__name__, __file__ + "\0")
+
 
 class ReloadTests(unittest.TestCase):
 
index 99fab58cb9326d070c6b399396002c050209da69..650d7373d8e1df802383c450f55a2348f2bcc791 100644 (file)
@@ -339,9 +339,14 @@ class TestCollation(unittest.TestCase):
         self.assertLess(locale.strcoll('a', 'b'), 0)
         self.assertEqual(locale.strcoll('a', 'a'), 0)
         self.assertGreater(locale.strcoll('b', 'a'), 0)
+        # embedded null character
+        self.assertRaises(ValueError, locale.strcoll, 'a\0', 'a')
+        self.assertRaises(ValueError, locale.strcoll, 'a', 'a\0')
 
     def test_strxfrm(self):
         self.assertLess(locale.strxfrm('a'), locale.strxfrm('b'))
+        # embedded null character
+        self.assertRaises(ValueError, locale.strxfrm, 'a\0')
 
 
 class TestEnUSCollation(BaseLocalizedTest, TestCollation):
index f2242126d1826d0764454fd3b85acfdf8a030788..810ec37b1113f9faddfda2e2cb286f6c1622c913 100644 (file)
@@ -126,6 +126,10 @@ class TimeTestCase(unittest.TestCase):
             except ValueError:
                 self.fail('conversion specifier: %r failed.' % format)
 
+        self.assertRaises(TypeError, time.strftime, b'%S', tt)
+        # embedded null character
+        self.assertRaises(ValueError, time.strftime, '%S\0', tt)
+
     def _bounds_checking(self, func):
         # Make sure that strftime() checks the bounds of the various parts
         # of the time tuple (0 is valid for *all* values).
index 179e069a1ccc1df23c88cb40a0c291136ab64646..21437ef5118cb0817862679e41020f863a9c5e10 100644 (file)
@@ -98,6 +98,8 @@ class PlaySoundTest(unittest.TestCase):
         self.assertRaises(TypeError, winsound.PlaySound, "bad",
                           winsound.SND_MEMORY)
         self.assertRaises(TypeError, winsound.PlaySound, 1, 0)
+        # embedded null character
+        self.assertRaises(ValueError, winsound.PlaySound, 'bad\0', 0)
 
     def test_keyword_args(self):
         safe_PlaySound(flags=winsound.SND_ALIAS, sound="SystemExit")
index 358f287bb7cd45f335d58ac42829dda88fcc8a9a..ff95dffb82c244fc472d51889837fa5d17628af8 100644 (file)
@@ -1231,14 +1231,15 @@ The handle may be used to locate exported functions in this\n\
 module.\n";
 static PyObject *load_library(PyObject *self, PyObject *args)
 {
-    WCHAR *name;
+    const WCHAR *name;
     PyObject *nameobj;
     PyObject *ignored;
     HMODULE hMod;
-    if (!PyArg_ParseTuple(args, "O|O:LoadLibrary", &nameobj, &ignored))
+
+    if (!PyArg_ParseTuple(args, "U|O:LoadLibrary", &nameobj, &ignored))
         return NULL;
 
-    name = PyUnicode_AsUnicode(nameobj);
+    name = _PyUnicode_AsUnicode(nameobj);
     if (!name)
         return NULL;
 
index 41b831e58f35a6c8e6add203a079ce53ebb6f945..7a7095118a0bafd8fe244deafa533a778f344291 100644 (file)
@@ -342,6 +342,7 @@ static int
 PyCurses_ConvertToString(PyCursesWindowObject *win, PyObject *obj,
                          PyObject **bytes, wchar_t **wstr)
 {
+    char *str;
     if (PyUnicode_Check(obj)) {
 #ifdef HAVE_NCURSESW
         assert (wstr != NULL);
@@ -354,12 +355,20 @@ PyCurses_ConvertToString(PyCursesWindowObject *win, PyObject *obj,
         *bytes = PyUnicode_AsEncodedString(obj, win->encoding, NULL);
         if (*bytes == NULL)
             return 0;
+        /* check for embedded null bytes */
+        if (PyBytes_AsStringAndSize(*bytes, &str, NULL) < 0) {
+            return 0;
+        }
         return 1;
 #endif
     }
     else if (PyBytes_Check(obj)) {
         Py_INCREF(obj);
         *bytes = obj;
+        /* check for embedded null bytes */
+        if (PyBytes_AsStringAndSize(*bytes, &str, NULL) < 0) {
+            return 0;
+        }
         return 1;
     }
 
index 833ea8e7b50894c954e733a5e651f87fbdad5036..918fa577758d2807f3e8afb9540c0c3486edc049 100644 (file)
@@ -280,11 +280,10 @@ _io_FileIO___init___impl(fileio *self, PyObject *nameobj, const char *mode,
 
     if (fd < 0) {
 #ifdef MS_WINDOWS
-        Py_ssize_t length;
         if (!PyUnicode_FSDecoder(nameobj, &stringobj)) {
             return -1;
         }
-        widename = PyUnicode_AsUnicodeAndSize(stringobj, &length);
+        widename = PyUnicode_AsUnicode(stringobj);
         if (widename == NULL)
             return -1;
 #else
index 0c7c3cd507fc68c0602f9d50750a5273b2b44f87..71c9146ccb8f47b975d02be1c8d0e38ae8f4544c 100644 (file)
@@ -252,6 +252,11 @@ PyLocale_strxfrm(PyObject* self, PyObject* args)
     s = PyUnicode_AsWideCharString(str, &n1);
     if (s == NULL)
         goto exit;
+    if (wcslen(s) != (size_t)n1) {
+        PyErr_SetString(PyExc_ValueError,
+                        "embedded null character");
+        goto exit;
+    }
 
     /* assume no change in size, first */
     n1 = n1 + 1;
index 9437ae7a5b1a14ea5d542302448bf9a5de7ccea9..f577fd3ab4ec3baeabcd09499d62229d457ad273 100644 (file)
@@ -151,6 +151,7 @@ grp_getgrnam_impl(PyObject *module, PyObject *name)
 
     if ((bytes = PyUnicode_EncodeFSDefault(name)) == NULL)
         return NULL;
+    /* check for embedded null bytes */
     if (PyBytes_AsStringAndSize(bytes, &name_chars, NULL) == -1)
         goto out;
 
index b6a855c5775c748b9133407954b6bab54832aae7..a9028bbfe54c6c71d764c76e2de7d2ab9741c4f8 100644 (file)
@@ -169,6 +169,7 @@ nis_match (PyObject *self, PyObject *args, PyObject *kwdict)
         return NULL;
     if ((bkey = PyUnicode_EncodeFSDefault(ukey)) == NULL)
         return NULL;
+    /* check for embedded null bytes */
     if (PyBytes_AsStringAndSize(bkey, &key, &keylen) == -1) {
         Py_DECREF(bkey);
         return NULL;
index bf8254311e02dd049f2508320fc1bbd0ff2e3473..4607b181c6cea6d91834bd7df706daa9c345fabf 100644 (file)
@@ -3680,7 +3680,7 @@ os__getfinalpathname_impl(PyObject *module, PyObject *path)
     PyObject *result;
     const wchar_t *path_wchar;
 
-    path_wchar = PyUnicode_AsUnicode(path);
+    path_wchar = _PyUnicode_AsUnicode(path);
     if (path_wchar == NULL)
         return NULL;
 
@@ -7088,7 +7088,7 @@ win_readlink(PyObject *self, PyObject *args, PyObject *kwargs)
                           ))
         return NULL;
 
-    path = PyUnicode_AsUnicode(po);
+    path = _PyUnicode_AsUnicode(po);
     if (path == NULL)
         return NULL;
 
@@ -8881,6 +8881,7 @@ os_putenv_impl(PyObject *module, PyObject *name, PyObject *value)
 /*[clinic end generated code: output=d29a567d6b2327d2 input=ba586581c2e6105f]*/
 {
     const wchar_t *env;
+    Py_ssize_t size;
 
     /* Search from index 1 because on Windows starting '=' is allowed for
        defining hidden environment variables. */
@@ -8894,16 +8895,21 @@ os_putenv_impl(PyObject *module, PyObject *name, PyObject *value)
     if (unicode == NULL) {
         return NULL;
     }
-    if (_MAX_ENV < PyUnicode_GET_LENGTH(unicode)) {
+
+    env = PyUnicode_AsUnicodeAndSize(unicode, &size);
+    if (env == NULL)
+        goto error;
+    if (size > _MAX_ENV) {
         PyErr_Format(PyExc_ValueError,
                      "the environment variable is longer than %u characters",
                      _MAX_ENV);
         goto error;
     }
-
-    env = PyUnicode_AsUnicode(unicode);
-    if (env == NULL)
+    if (wcslen(env) != (size_t)size) {
+        PyErr_SetString(PyExc_ValueError, "embedded null character");
         goto error;
+    }
+
     if (_wputenv(env)) {
         posix_error();
         goto error;
index 784e9d0349fa790dbd9ff5adb3cc4fb2d4525cd6..bbef2de9c52253287f7dc068d798d33044f674c8 100644 (file)
@@ -158,6 +158,7 @@ pwd_getpwnam_impl(PyObject *module, PyObject *arg)
 
     if ((bytes = PyUnicode_EncodeFSDefault(arg)) == NULL)
         return NULL;
+    /* check for embedded null bytes */
     if (PyBytes_AsStringAndSize(bytes, &name, NULL) == -1)
         goto out;
     if ((p = getpwnam(name)) == NULL) {
index 556a715a242482dfd14fcbfc11e9f4f161cb7237..1601ec0f2fc4e726c248498c2315c091f497f88e 100644 (file)
@@ -134,6 +134,7 @@ spwd_getspnam_impl(PyObject *module, PyObject *arg)
 
     if ((bytes = PyUnicode_EncodeFSDefault(arg)) == NULL)
         return NULL;
+    /* check for embedded null bytes */
     if (PyBytes_AsStringAndSize(bytes, &name, NULL) == -1)
         goto out;
     if ((p = getspnam(name)) == NULL) {
index 3767064e50498177494d406b896a69b0fa2cc633..494cdbd42e033bfbe2b094094270de715a541ced 100644 (file)
@@ -4164,6 +4164,20 @@ PyUnicode_AsUnicode(PyObject *unicode)
     return PyUnicode_AsUnicodeAndSize(unicode, NULL);
 }
 
+const Py_UNICODE *
+_PyUnicode_AsUnicode(PyObject *unicode)
+{
+    Py_ssize_t size;
+    const Py_UNICODE *wstr;
+
+    wstr = PyUnicode_AsUnicodeAndSize(unicode, &size);
+    if (wstr && wcslen(wstr) != (size_t)size) {
+        PyErr_SetString(PyExc_ValueError, "embedded null character");
+        return NULL;
+    }
+    return wstr;
+}
+
 
 Py_ssize_t
 PyUnicode_GetSize(PyObject *unicode)
index 789b04f5a251775631c4f57c6927ba2d97f68949..15aa7d8fecf32455abbb55129ab1925e66532c5e 100644 (file)
--- a/PC/_msi.c
+++ b/PC/_msi.c
@@ -600,8 +600,12 @@ summary_setproperty(msiobj* si, PyObject *args)
         return NULL;
 
     if (PyUnicode_Check(data)) {
+        const WCHAR *value = _PyUnicode_AsUnicode(data);
+        if (value == NULL) {
+            return NULL;
+        }
         status = MsiSummaryInfoSetPropertyW(si->h, field, VT_LPSTR,
-            0, NULL, PyUnicode_AsUnicode(data));
+            0, NULL, value);
     } else if (PyLong_CheckExact(data)) {
         long value = PyLong_AsLong(data);
         if (value == -1 && PyErr_Occurred()) {
index 05050cf41d2110a84936bef448543f6f9a9329fa..0fdf77f55280ca43c9696a8486d2e9350571d07c 100644 (file)
@@ -190,13 +190,13 @@ dl_funcptr _PyImport_FindSharedFuncptrWindows(const char *prefix,
 {
     dl_funcptr p;
     char funcname[258], *import_python;
-    wchar_t *wpathname;
+    const wchar_t *wpathname;
 
 #ifndef _DEBUG
     _Py_CheckPython3();
 #endif
 
-    wpathname = PyUnicode_AsUnicode(pathname);
+    wpathname = _PyUnicode_AsUnicode(pathname);
     if (wpathname == NULL)
         return NULL;
 
index f3764e4b3c5a2d09b8d277c30d4ef75d7670a605..97505e5bc6d3ced71a4732b50efb8cf004c42508 100644 (file)
@@ -711,21 +711,32 @@ _Py_stat(PyObject *path, struct stat *statbuf)
 #ifdef MS_WINDOWS
     int err;
     struct _stat wstatbuf;
-    wchar_t *wpath;
+    const wchar_t *wpath;
 
-    wpath = PyUnicode_AsUnicode(path);
+    wpath = _PyUnicode_AsUnicode(path);
     if (wpath == NULL)
         return -2;
+
     err = _wstat(wpath, &wstatbuf);
     if (!err)
         statbuf->st_mode = wstatbuf.st_mode;
     return err;
 #else
     int ret;
-    PyObject *bytes = PyUnicode_EncodeFSDefault(path);
+    PyObject *bytes;
+    char *cpath;
+
+    bytes = PyUnicode_EncodeFSDefault(path);
     if (bytes == NULL)
         return -2;
-    ret = stat(PyBytes_AS_STRING(bytes), statbuf);
+
+    /* check for embedded null bytes */
+    if (PyBytes_AsStringAndSize(bytes, &cpath, NULL) == -1) {
+        Py_DECREF(bytes);
+        return -2;
+    }
+
+    ret = stat(cpath, statbuf);
     Py_DECREF(bytes);
     return ret;
 #endif
@@ -1080,7 +1091,7 @@ _Py_fopen_obj(PyObject *path, const char *mode)
     FILE *f;
     int async_err = 0;
 #ifdef MS_WINDOWS
-    wchar_t *wpath;
+    const wchar_t *wpath;
     wchar_t wmode[10];
     int usize;
 
@@ -1094,7 +1105,7 @@ _Py_fopen_obj(PyObject *path, const char *mode)
                      Py_TYPE(path));
         return NULL;
     }
-    wpath = PyUnicode_AsUnicode(path);
+    wpath = _PyUnicode_AsUnicode(path);
     if (wpath == NULL)
         return NULL;