Skip to content

Commit e713575

Browse files
[3.6] bpo-30730: Prevent environment variables injection in subprocess on Windows. (GH-2325) (#2360)
Prevent passing other invalid environment variables and command arguments.. (cherry picked from commit d174d24)
1 parent 1b7474d commit e713575

File tree

5 files changed

+72
-9
lines changed

5 files changed

+72
-9
lines changed

Lib/subprocess.py

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

12411241
if env is not None:
1242-
env_list = [os.fsencode(k) + b'=' + os.fsencode(v)
1243-
for k, v in env.items()]
1242+
env_list = []
1243+
for k, v in env.items():
1244+
k = os.fsencode(k)
1245+
if b'=' in k:
1246+
raise ValueError("illegal environment variable name")
1247+
env_list.append(k + b'=' + os.fsencode(v))
12441248
else:
12451249
env_list = None # Use execv instead of execve.
12461250
executable = os.fsencode(executable)

Lib/test/test_subprocess.py

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

647+
def test_invalid_cmd(self):
648+
# null character in the command name
649+
cmd = sys.executable + '\0'
650+
with self.assertRaises(ValueError):
651+
subprocess.Popen([cmd, "-c", "pass"])
652+
653+
# null character in the command argument
654+
with self.assertRaises(ValueError):
655+
subprocess.Popen([sys.executable, "-c", "pass#\0"])
656+
657+
def test_invalid_env(self):
658+
# null character in the enviroment variable name
659+
newenv = os.environ.copy()
660+
newenv["FRUIT\0VEGETABLE"] = "cabbage"
661+
with self.assertRaises(ValueError):
662+
subprocess.Popen([sys.executable, "-c", "pass"], env=newenv)
663+
664+
# null character in the enviroment variable value
665+
newenv = os.environ.copy()
666+
newenv["FRUIT"] = "orange\0VEGETABLE=cabbage"
667+
with self.assertRaises(ValueError):
668+
subprocess.Popen([sys.executable, "-c", "pass"], env=newenv)
669+
670+
# equal character in the enviroment variable name
671+
newenv = os.environ.copy()
672+
newenv["FRUIT=ORANGE"] = "lemon"
673+
with self.assertRaises(ValueError):
674+
subprocess.Popen([sys.executable, "-c", "pass"], env=newenv)
675+
676+
# equal character in the enviroment variable value
677+
newenv = os.environ.copy()
678+
newenv["FRUIT"] = "orange=lemon"
679+
with subprocess.Popen([sys.executable, "-c",
680+
'import sys, os;'
681+
'sys.stdout.write(os.getenv("FRUIT"))'],
682+
stdout=subprocess.PIPE,
683+
env=newenv) as p:
684+
stdout, stderr = p.communicate()
685+
self.assertEqual(stdout, b"orange=lemon")
686+
647687
def test_communicate_stdin(self):
648688
p = subprocess.Popen([sys.executable, "-c",
649689
'import sys;'

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ Core and Builtins
1313
Library
1414
-------
1515

16+
- [Security] bpo-30730: Prevent environment variables injection in subprocess on
17+
Windows. Prevent passing other environment variables and command arguments.
18+
1619
- [Security] bpo-30694: Upgrade expat copy from 2.2.0 to 2.2.1 to get fixes
1720
of multiple security vulnerabilities including: CVE-2017-9233 (External
1821
entity infinite loop DoS), CVE-2016-9063 (Integer overflow, re-fix),

Modules/_winapi.c

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,20 @@ getenvironment(PyObject* environment)
744744
"environment can only contain strings");
745745
goto error;
746746
}
747+
if (PyUnicode_FindChar(key, '\0', 0, PyUnicode_GET_LENGTH(key), 1) != -1 ||
748+
PyUnicode_FindChar(value, '\0', 0, PyUnicode_GET_LENGTH(value), 1) != -1)
749+
{
750+
PyErr_SetString(PyExc_ValueError, "embedded null character");
751+
goto error;
752+
}
753+
/* Search from index 1 because on Windows starting '=' is allowed for
754+
defining hidden environment variables. */
755+
if (PyUnicode_GET_LENGTH(key) == 0 ||
756+
PyUnicode_FindChar(key, '=', 1, PyUnicode_GET_LENGTH(key), 1) != -1)
757+
{
758+
PyErr_SetString(PyExc_ValueError, "illegal environment variable name");
759+
goto error;
760+
}
747761
if (totalsize > PY_SSIZE_T_MAX - PyUnicode_GET_LENGTH(key) - 1) {
748762
PyErr_SetString(PyExc_OverflowError, "environment too long");
749763
goto error;
@@ -830,7 +844,8 @@ _winapi_CreateProcess_impl(PyObject *module, Py_UNICODE *application_name,
830844
PROCESS_INFORMATION pi;
831845
STARTUPINFOW si;
832846
PyObject* environment;
833-
wchar_t *wenvironment;
847+
const wchar_t *wenvironment;
848+
Py_ssize_t wenvironment_size;
834849

835850
ZeroMemory(&si, sizeof(si));
836851
si.cb = sizeof(si);
@@ -846,12 +861,13 @@ _winapi_CreateProcess_impl(PyObject *module, Py_UNICODE *application_name,
846861

847862
if (env_mapping != Py_None) {
848863
environment = getenvironment(env_mapping);
849-
if (! environment)
864+
if (environment == NULL) {
850865
return NULL;
866+
}
867+
/* contains embedded null characters */
851868
wenvironment = PyUnicode_AsUnicode(environment);
852-
if (wenvironment == NULL)
853-
{
854-
Py_XDECREF(environment);
869+
if (wenvironment == NULL) {
870+
Py_DECREF(environment);
855871
return NULL;
856872
}
857873
}

Objects/abstract.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3193,8 +3193,8 @@ _PySequence_BytesToCharpArray(PyObject* self)
31933193
array[i] = NULL;
31943194
goto fail;
31953195
}
3196-
data = PyBytes_AsString(item);
3197-
if (data == NULL) {
3196+
/* check for embedded null bytes */
3197+
if (PyBytes_AsStringAndSize(item, &data, NULL) < 0) {
31983198
/* NULL terminate before freeing. */
31993199
array[i] = NULL;
32003200
goto fail;

0 commit comments

Comments
 (0)