Skip to content

Commit 787826c

Browse files
[2.7] bpo-30746: Prohibited the '=' character in environment variable names (GH-2382) (#2393)
in `os.putenv()` and `os.spawn*()`.. (cherry picked from commit 7770394)
1 parent 9dda2ca commit 787826c

File tree

4 files changed

+112
-3
lines changed

4 files changed

+112
-3
lines changed

‎Lib/test/test_os.py‎

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -599,11 +599,32 @@ def test_urandom_failure(self):
599599
assert_python_ok('-c', code)
600600

601601

602-
class ExecvpeTests(unittest.TestCase):
602+
class ExecTests(unittest.TestCase):
603603

604604
def test_execvpe_with_bad_arglist(self):
605605
self.assertRaises(ValueError, os.execvpe, 'notepad', [], None)
606606

607+
def test_execve_invalid_env(self):
608+
args = [sys.executable, '-c', 'pass']
609+
610+
# null character in the enviroment variable name
611+
newenv = os.environ.copy()
612+
newenv["FRUIT\0VEGETABLE"] = "cabbage"
613+
with self.assertRaises(TypeError):
614+
os.execve(args[0], args, newenv)
615+
616+
# null character in the enviroment variable value
617+
newenv = os.environ.copy()
618+
newenv["FRUIT"] = "orange\0VEGETABLE=cabbage"
619+
with self.assertRaises(TypeError):
620+
os.execve(args[0], args, newenv)
621+
622+
# equal character in the enviroment variable name
623+
newenv = os.environ.copy()
624+
newenv["FRUIT=ORANGE"] = "lemon"
625+
with self.assertRaises(ValueError):
626+
os.execve(args[0], args, newenv)
627+
607628

608629
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
609630
class Win32ErrorTests(unittest.TestCase):
@@ -879,6 +900,62 @@ def test_CTRL_BREAK_EVENT(self):
879900
self._kill_with_event(signal.CTRL_BREAK_EVENT, "CTRL_BREAK_EVENT")
880901

881902

903+
class SpawnTests(unittest.TestCase):
904+
def _test_invalid_env(self, spawn):
905+
args = [sys.executable, '-c', 'pass']
906+
907+
# null character in the enviroment variable name
908+
newenv = os.environ.copy()
909+
newenv["FRUIT\0VEGETABLE"] = "cabbage"
910+
try:
911+
exitcode = spawn(os.P_WAIT, args[0], args, newenv)
912+
except TypeError:
913+
pass
914+
else:
915+
self.assertEqual(exitcode, 127)
916+
917+
# null character in the enviroment variable value
918+
newenv = os.environ.copy()
919+
newenv["FRUIT"] = "orange\0VEGETABLE=cabbage"
920+
try:
921+
exitcode = spawn(os.P_WAIT, args[0], args, newenv)
922+
except TypeError:
923+
pass
924+
else:
925+
self.assertEqual(exitcode, 127)
926+
927+
# equal character in the enviroment variable name
928+
newenv = os.environ.copy()
929+
newenv["FRUIT=ORANGE"] = "lemon"
930+
try:
931+
exitcode = spawn(os.P_WAIT, args[0], args, newenv)
932+
except ValueError:
933+
pass
934+
else:
935+
self.assertEqual(exitcode, 127)
936+
937+
# equal character in the enviroment variable value
938+
filename = test_support.TESTFN
939+
self.addCleanup(test_support.unlink, filename)
940+
with open(filename, "w") as fp:
941+
fp.write('import sys, os\n'
942+
'if os.getenv("FRUIT") != "orange=lemon":\n'
943+
' raise AssertionError')
944+
args = [sys.executable, filename]
945+
newenv = os.environ.copy()
946+
newenv["FRUIT"] = "orange=lemon"
947+
exitcode = spawn(os.P_WAIT, args[0], args, newenv)
948+
self.assertEqual(exitcode, 0)
949+
950+
@unittest.skipUnless(hasattr(os, 'spawnve'), 'test needs os.spawnve()')
951+
def test_spawnve_invalid_env(self):
952+
self._test_invalid_env(os.spawnve)
953+
954+
@unittest.skipUnless(hasattr(os, 'spawnvpe'), 'test needs os.spawnvpe()')
955+
def test_spawnvpe_invalid_env(self):
956+
self._test_invalid_env(os.spawnvpe)
957+
958+
882959
def test_main():
883960
test_support.run_unittest(
884961
FileTests,
@@ -890,11 +967,12 @@ def test_main():
890967
DevNullTests,
891968
URandomTests,
892969
URandomFDTests,
893-
ExecvpeTests,
970+
ExecTests,
894971
Win32ErrorTests,
895972
TestInvalidFD,
896973
PosixUidGidTests,
897-
Win32KillTests
974+
Win32KillTests,
975+
SpawnTests,
898976
)
899977

900978
if __name__ == "__main__":

‎Lib/test/test_posix.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,15 @@ def test_lchflags_symlink(self):
504504
finally:
505505
posix.lchflags(_DUMMY_SYMLINK, dummy_symlink_st.st_flags)
506506

507+
@unittest.skipUnless(hasattr(os, "putenv"), "requires os.putenv()")
508+
def test_putenv(self):
509+
with self.assertRaises(TypeError):
510+
os.putenv('FRUIT\0VEGETABLE', 'cabbage')
511+
with self.assertRaises(TypeError):
512+
os.putenv('FRUIT', 'orange\0VEGETABLE=cabbage')
513+
with self.assertRaises(ValueError):
514+
os.putenv('FRUIT=ORANGE', 'lemon')
515+
507516
@unittest.skipUnless(hasattr(posix, 'getcwd'),
508517
'test needs posix.getcwd()')
509518
def test_getcwd_long_pathnames(self):

‎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+
- bpo-30746: Prohibited the '=' character in environment variable names in
56+
``os.putenv()`` and ``os.spawn*()``.
57+
5558
- [Security] bpo-30730: Prevent environment variables injection in subprocess on
5659
Windows. Prevent passing other environment variables and command arguments.
5760

‎Modules/posixmodule.c‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3556,6 +3556,12 @@ posix_spawnve(PyObject *self, PyObject *args)
35563556
{
35573557
goto fail_2;
35583558
}
3559+
/* Search from index 1 because on Windows starting '=' is allowed for
3560+
defining hidden environment variables. */
3561+
if (*k == '\0' || strchr(k + 1, '=') != NULL) {
3562+
PyErr_SetString(PyExc_ValueError, "illegal environment variable name");
3563+
goto fail_2;
3564+
}
35593565
len = PyString_Size(key) + PyString_Size(val) + 2;
35603566
p = PyMem_NEW(char, len);
35613567
if (p == NULL) {
@@ -3789,6 +3795,12 @@ posix_spawnvpe(PyObject *self, PyObject *args)
37893795
{
37903796
goto fail_2;
37913797
}
3798+
/* Search from index 1 because on Windows starting '=' is allowed for
3799+
defining hidden environment variables. */
3800+
if (*k == '\0' || strchr(k + 1, '=') != NULL) {
3801+
PyErr_SetString(PyExc_ValueError, "illegal environment variable name");
3802+
goto fail_2;
3803+
}
37923804
len = PyString_Size(key) + PyString_Size(val) + 2;
37933805
p = PyMem_NEW(char, len);
37943806
if (p == NULL) {
@@ -7185,6 +7197,13 @@ posix_putenv(PyObject *self, PyObject *args)
71857197
} else {
71867198
#endif
71877199

7200+
/* Search from index 1 because on Windows starting '=' is allowed for
7201+
defining hidden environment variables. */
7202+
if (*s1 == '\0' || strchr(s1 + 1, '=') != NULL) {
7203+
PyErr_SetString(PyExc_ValueError, "illegal environment variable name");
7204+
return NULL;
7205+
}
7206+
71887207
/* XXX This can leak memory -- not easy to fix :-( */
71897208
len = strlen(s1) + strlen(s2) + 2;
71907209
#ifdef MS_WINDOWS

0 commit comments

Comments
 (0)