Skip to content

Commit 9dda2ca

Browse files
[2.7] bpo-30730: Prevent environment variables injection in subprocess on Windows. (GH-2325) (#2372)
Prevent passing other invalid environment variables and command arguments.. (cherry picked from commit d174d24)
1 parent 7709b4d commit 9dda2ca

File tree

4 files changed

+63
-2
lines changed

4 files changed

+63
-2
lines changed

Lib/test/test_subprocess.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,46 @@ def test_env(self):
385385
self.addCleanup(p.stdout.close)
386386
self.assertEqual(p.stdout.read(), "orange")
387387

388+
def test_invalid_cmd(self):
389+
# null character in the command name
390+
cmd = sys.executable + '\0'
391+
with self.assertRaises(TypeError):
392+
subprocess.Popen([cmd, "-c", "pass"])
393+
394+
# null character in the command argument
395+
with self.assertRaises(TypeError):
396+
subprocess.Popen([sys.executable, "-c", "pass#\0"])
397+
398+
def test_invalid_env(self):
399+
# null character in the enviroment variable name
400+
newenv = os.environ.copy()
401+
newenv["FRUIT\0VEGETABLE"] = "cabbage"
402+
with self.assertRaises(TypeError):
403+
subprocess.Popen([sys.executable, "-c", "pass"], env=newenv)
404+
405+
# null character in the enviroment variable value
406+
newenv = os.environ.copy()
407+
newenv["FRUIT"] = "orange\0VEGETABLE=cabbage"
408+
with self.assertRaises(TypeError):
409+
subprocess.Popen([sys.executable, "-c", "pass"], env=newenv)
410+
411+
# equal character in the enviroment variable name
412+
newenv = os.environ.copy()
413+
newenv["FRUIT=ORANGE"] = "lemon"
414+
with self.assertRaises(ValueError):
415+
subprocess.Popen([sys.executable, "-c", "pass"], env=newenv)
416+
417+
# equal character in the enviroment variable value
418+
newenv = os.environ.copy()
419+
newenv["FRUIT"] = "orange=lemon"
420+
p = subprocess.Popen([sys.executable, "-c",
421+
'import sys, os;'
422+
'sys.stdout.write(os.getenv("FRUIT"))'],
423+
stdout=subprocess.PIPE,
424+
env=newenv)
425+
stdout, stderr = p.communicate()
426+
self.assertEqual(stdout, "orange=lemon")
427+
388428
def test_communicate_stdin(self):
389429
p = subprocess.Popen([sys.executable, "-c",
390430
'import sys;'

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ Extension Modules
5252
Library
5353
-------
5454

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

Modules/posixmodule.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3315,6 +3315,12 @@ posix_execve(PyObject *self, PyObject *args)
33153315
{
33163316
goto fail_2;
33173317
}
3318+
/* Search from index 1 because on Windows starting '=' is allowed for
3319+
defining hidden environment variables. */
3320+
if (*k == '\0' || strchr(k + 1, '=') != NULL) {
3321+
PyErr_SetString(PyExc_ValueError, "illegal environment variable name");
3322+
goto fail_2;
3323+
}
33183324

33193325
#if defined(PYOS_OS2)
33203326
/* Omit Pseudo-Env Vars that Would Confuse Programs if Passed On */

PC/_subprocess.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ getenvironment(PyObject* environment)
352352
p = PyString_AS_STRING(out);
353353

354354
for (i = 0; i < envsize; i++) {
355-
int ksize, vsize, totalsize;
355+
size_t ksize, vsize, totalsize;
356356
PyObject* key = PyList_GET_ITEM(keys, i);
357357
PyObject* value = PyList_GET_ITEM(values, i);
358358

@@ -363,10 +363,22 @@ getenvironment(PyObject* environment)
363363
}
364364
ksize = PyString_GET_SIZE(key);
365365
vsize = PyString_GET_SIZE(value);
366+
if (strlen(PyString_AS_STRING(key)) != ksize ||
367+
strlen(PyString_AS_STRING(value)) != vsize)
368+
{
369+
PyErr_SetString(PyExc_TypeError, "embedded null character");
370+
goto error;
371+
}
372+
/* Search from index 1 because on Windows starting '=' is allowed for
373+
defining hidden environment variables. */
374+
if (ksize == 0 || strchr(PyString_AS_STRING(key) + 1, '=') != NULL) {
375+
PyErr_SetString(PyExc_ValueError, "illegal environment variable name");
376+
goto error;
377+
}
366378
totalsize = (p - PyString_AS_STRING(out)) + ksize + 1 +
367379
vsize + 1 + 1;
368380
if (totalsize > PyString_GET_SIZE(out)) {
369-
int offset = p - PyString_AS_STRING(out);
381+
size_t offset = p - PyString_AS_STRING(out);
370382
if (_PyString_Resize(&out, totalsize + 1024))
371383
goto exit;
372384
p = PyString_AS_STRING(out) + offset;

0 commit comments

Comments
 (0)