Skip to content

Commit fe82c46

Browse files
serhiy-storchakalarryhastings
authored andcommitted
[security][3.4] bpo-30730: Prevent environment variables injection in subprocess on Windows. (GH-2325) (#2362)
* [3.4] bpo-30730: Prevent environment variables injection in subprocess on Windows. (GH-2325) Prevent passing other invalid environment variables and command arguments.. (cherry picked from commit d174d24) * Update NEWS
1 parent ad1fb81 commit fe82c46

File tree

5 files changed

+71
-8
lines changed

5 files changed

+71
-8
lines changed

Lib/subprocess.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,8 +1375,12 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
13751375
# and pass it to fork_exec()
13761376

13771377
if env is not None:
1378-
env_list = [os.fsencode(k) + b'=' + os.fsencode(v)
1379-
for k, v in env.items()]
1378+
env_list = []
1379+
for k, v in env.items():
1380+
k = os.fsencode(k)
1381+
if b'=' in k:
1382+
raise ValueError("illegal environment variable name")
1383+
env_list.append(k + b'=' + os.fsencode(v))
13801384
else:
13811385
env_list = None # Use execv instead of execve.
13821386
executable = os.fsencode(executable)

Lib/test/test_subprocess.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,46 @@ def test_empty_env(self):
606606
# environment
607607
b"['__CF_USER_TEXT_ENCODING']"))
608608

609+
def test_invalid_cmd(self):
610+
# null character in the command name
611+
cmd = sys.executable + '\0'
612+
with self.assertRaises(ValueError):
613+
subprocess.Popen([cmd, "-c", "pass"])
614+
615+
# null character in the command argument
616+
with self.assertRaises(ValueError):
617+
subprocess.Popen([sys.executable, "-c", "pass#\0"])
618+
619+
def test_invalid_env(self):
620+
# null character in the enviroment variable name
621+
newenv = os.environ.copy()
622+
newenv["FRUIT\0VEGETABLE"] = "cabbage"
623+
with self.assertRaises(ValueError):
624+
subprocess.Popen([sys.executable, "-c", "pass"], env=newenv)
625+
626+
# null character in the enviroment variable value
627+
newenv = os.environ.copy()
628+
newenv["FRUIT"] = "orange\0VEGETABLE=cabbage"
629+
with self.assertRaises(ValueError):
630+
subprocess.Popen([sys.executable, "-c", "pass"], env=newenv)
631+
632+
# equal character in the enviroment variable name
633+
newenv = os.environ.copy()
634+
newenv["FRUIT=ORANGE"] = "lemon"
635+
with self.assertRaises(ValueError):
636+
subprocess.Popen([sys.executable, "-c", "pass"], env=newenv)
637+
638+
# equal character in the enviroment variable value
639+
newenv = os.environ.copy()
640+
newenv["FRUIT"] = "orange=lemon"
641+
with subprocess.Popen([sys.executable, "-c",
642+
'import sys, os;'
643+
'sys.stdout.write(os.getenv("FRUIT"))'],
644+
stdout=subprocess.PIPE,
645+
env=newenv) as p:
646+
stdout, stderr = p.communicate()
647+
self.assertEqual(stdout, b"orange=lemon")
648+
609649
def test_communicate_stdin(self):
610650
p = subprocess.Popen([sys.executable, "-c",
611651
'import sys;'

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@ Documentation
1919
Library
2020
-------
2121

22+
- [Security] bpo-30730: Prevent environment variables injection in subprocess on
23+
Windows. Prevent passing other invalid environment variables and command arguments.
24+
2225
- Issue #27850: Remove 3DES from ssl module's default cipher list to counter
2326
measure sweet32 attack (CVE-2016-2183).
2427

28+
2529
What's New in Python 3.4.6?
2630
===========================
2731

Modules/_winapi.c

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,20 @@ getenvironment(PyObject* environment)
535535
"environment can only contain strings");
536536
goto error;
537537
}
538+
if (PyUnicode_FindChar(key, '\0', 0, PyUnicode_GET_LENGTH(key), 1) != -1 ||
539+
PyUnicode_FindChar(value, '\0', 0, PyUnicode_GET_LENGTH(value), 1) != -1)
540+
{
541+
PyErr_SetString(PyExc_ValueError, "embedded null character");
542+
goto error;
543+
}
544+
/* Search from index 1 because on Windows starting '=' is allowed for
545+
defining hidden environment variables. */
546+
if (PyUnicode_GET_LENGTH(key) == 0 ||
547+
PyUnicode_FindChar(key, '=', 1, PyUnicode_GET_LENGTH(key), 1) != -1)
548+
{
549+
PyErr_SetString(PyExc_ValueError, "illegal environment variable name");
550+
goto error;
551+
}
538552
if (totalsize > PY_SSIZE_T_MAX - PyUnicode_GET_LENGTH(key) - 1) {
539553
PyErr_SetString(PyExc_OverflowError, "environment too long");
540554
goto error;
@@ -643,12 +657,13 @@ winapi_CreateProcess(PyObject* self, PyObject* args)
643657

644658
if (env_mapping != Py_None) {
645659
environment = getenvironment(env_mapping);
646-
if (! environment)
660+
if (environment == NULL) {
647661
return NULL;
662+
}
663+
/* contains embedded null characters */
648664
wenvironment = PyUnicode_AsUnicode(environment);
649-
if (wenvironment == NULL)
650-
{
651-
Py_XDECREF(environment);
665+
if (wenvironment == NULL) {
666+
Py_DECREF(environment);
652667
return NULL;
653668
}
654669
}

Objects/abstract.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2714,8 +2714,8 @@ _PySequence_BytesToCharpArray(PyObject* self)
27142714
array[i] = NULL;
27152715
goto fail;
27162716
}
2717-
data = PyBytes_AsString(item);
2718-
if (data == NULL) {
2717+
/* check for embedded null bytes */
2718+
if (PyBytes_AsStringAndSize(item, &data, NULL) < 0) {
27192719
/* NULL terminate before freeing. */
27202720
array[i] = NULL;
27212721
goto fail;

0 commit comments

Comments
 (0)