[security] bpo-13617: Reject embedded null characters in wchar* strings. (#2302)

Based on patch by Victor Stinner.

Add private C API function _PyUnicode_AsUnicode() which is similar to
PyUnicode_AsUnicode(), but checks for null characters.
diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c
index 5439b93..6990a76 100644
--- a/Modules/_ctypes/callproc.c
+++ b/Modules/_ctypes/callproc.c
@@ -1253,10 +1253,11 @@
     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;
 
diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c
index 5dc0865..a709151 100644
--- a/Modules/_cursesmodule.c
+++ b/Modules/_cursesmodule.c
@@ -341,9 +341,11 @@
 PyCurses_ConvertToString(PyCursesWindowObject *win, PyObject *obj,
                          PyObject **bytes, wchar_t **wstr)
 {
+    char *str;
     if (PyUnicode_Check(obj)) {
 #ifdef HAVE_NCURSESW
         assert (wstr != NULL);
+
         *wstr = PyUnicode_AsWideCharString(obj, NULL);
         if (*wstr == NULL)
             return 0;
@@ -353,12 +355,20 @@
         *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;
     }
 
diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c
index a09c39f7..cc7061f 100644
--- a/Modules/_io/fileio.c
+++ b/Modules/_io/fileio.c
@@ -271,11 +271,10 @@
 
     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
diff --git a/Modules/_localemodule.c b/Modules/_localemodule.c
index ecd673e..e364668 100644
--- a/Modules/_localemodule.c
+++ b/Modules/_localemodule.c
@@ -252,6 +252,11 @@
     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;
diff --git a/Modules/grpmodule.c b/Modules/grpmodule.c
index 9437ae7..f577fd3 100644
--- a/Modules/grpmodule.c
+++ b/Modules/grpmodule.c
@@ -151,6 +151,7 @@
 
     if ((bytes = PyUnicode_EncodeFSDefault(name)) == NULL)
         return NULL;
+    /* check for embedded null bytes */
     if (PyBytes_AsStringAndSize(bytes, &name_chars, NULL) == -1)
         goto out;
 
diff --git a/Modules/nismodule.c b/Modules/nismodule.c
index b6a855c..a9028bb 100644
--- a/Modules/nismodule.c
+++ b/Modules/nismodule.c
@@ -169,6 +169,7 @@
         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;
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 1c75eae..194a2b5 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -3757,7 +3757,7 @@
     PyObject *result;
     const wchar_t *path_wchar;
 
-    path_wchar = PyUnicode_AsUnicode(path);
+    path_wchar = _PyUnicode_AsUnicode(path);
     if (path_wchar == NULL)
         return NULL;
 
@@ -7209,7 +7209,7 @@
                           ))
         return NULL;
 
-    path = PyUnicode_AsUnicode(po);
+    path = _PyUnicode_AsUnicode(po);
     if (path == NULL)
         return NULL;
 
@@ -9002,6 +9002,7 @@
 /*[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. */
@@ -9015,16 +9016,21 @@
     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;
diff --git a/Modules/pwdmodule.c b/Modules/pwdmodule.c
index 784e9d0..bbef2de 100644
--- a/Modules/pwdmodule.c
+++ b/Modules/pwdmodule.c
@@ -158,6 +158,7 @@
 
     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) {
diff --git a/Modules/spwdmodule.c b/Modules/spwdmodule.c
index 556a715..1601ec0 100644
--- a/Modules/spwdmodule.c
+++ b/Modules/spwdmodule.c
@@ -134,6 +134,7 @@
 
     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) {