bpo-30730: Prevent environment variables injection in subprocess on Windows. (#2325)
Prevent passing other invalid environment variables and command arguments.
This commit is contained in:
parent
d352d68977
commit
d174d24a5d
|
@ -1238,8 +1238,12 @@ class Popen(object):
|
||||||
# and pass it to fork_exec()
|
# and pass it to fork_exec()
|
||||||
|
|
||||||
if env is not None:
|
if env is not None:
|
||||||
env_list = [os.fsencode(k) + b'=' + os.fsencode(v)
|
env_list = []
|
||||||
for k, v in env.items()]
|
for k, v in env.items():
|
||||||
|
k = os.fsencode(k)
|
||||||
|
if b'=' in k:
|
||||||
|
raise ValueError("illegal environment variable name")
|
||||||
|
env_list.append(k + b'=' + os.fsencode(v))
|
||||||
else:
|
else:
|
||||||
env_list = None # Use execv instead of execve.
|
env_list = None # Use execv instead of execve.
|
||||||
executable = os.fsencode(executable)
|
executable = os.fsencode(executable)
|
||||||
|
|
|
@ -655,6 +655,46 @@ class ProcessTestCase(BaseTestCase):
|
||||||
if not is_env_var_to_ignore(k)]
|
if not is_env_var_to_ignore(k)]
|
||||||
self.assertEqual(child_env_names, [])
|
self.assertEqual(child_env_names, [])
|
||||||
|
|
||||||
|
def test_invalid_cmd(self):
|
||||||
|
# null character in the command name
|
||||||
|
cmd = sys.executable + '\0'
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
subprocess.Popen([cmd, "-c", "pass"])
|
||||||
|
|
||||||
|
# null character in the command argument
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
subprocess.Popen([sys.executable, "-c", "pass#\0"])
|
||||||
|
|
||||||
|
def test_invalid_env(self):
|
||||||
|
# null character in the enviroment variable name
|
||||||
|
newenv = os.environ.copy()
|
||||||
|
newenv["FRUIT\0VEGETABLE"] = "cabbage"
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
subprocess.Popen([sys.executable, "-c", "pass"], env=newenv)
|
||||||
|
|
||||||
|
# null character in the enviroment variable value
|
||||||
|
newenv = os.environ.copy()
|
||||||
|
newenv["FRUIT"] = "orange\0VEGETABLE=cabbage"
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
subprocess.Popen([sys.executable, "-c", "pass"], env=newenv)
|
||||||
|
|
||||||
|
# equal character in the enviroment variable name
|
||||||
|
newenv = os.environ.copy()
|
||||||
|
newenv["FRUIT=ORANGE"] = "lemon"
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
subprocess.Popen([sys.executable, "-c", "pass"], env=newenv)
|
||||||
|
|
||||||
|
# equal character in the enviroment variable value
|
||||||
|
newenv = os.environ.copy()
|
||||||
|
newenv["FRUIT"] = "orange=lemon"
|
||||||
|
with subprocess.Popen([sys.executable, "-c",
|
||||||
|
'import sys, os;'
|
||||||
|
'sys.stdout.write(os.getenv("FRUIT"))'],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
env=newenv) as p:
|
||||||
|
stdout, stderr = p.communicate()
|
||||||
|
self.assertEqual(stdout, b"orange=lemon")
|
||||||
|
|
||||||
def test_communicate_stdin(self):
|
def test_communicate_stdin(self):
|
||||||
p = subprocess.Popen([sys.executable, "-c",
|
p = subprocess.Popen([sys.executable, "-c",
|
||||||
'import sys;'
|
'import sys;'
|
||||||
|
|
|
@ -374,6 +374,9 @@ Extension Modules
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- [Security] bpo-30730: Prevent environment variables injection in subprocess on
|
||||||
|
Windows. Prevent passing other environment variables and command arguments.
|
||||||
|
|
||||||
- bpo-21071: struct.Struct.format type is now :class:`str` instead of
|
- bpo-21071: struct.Struct.format type is now :class:`str` instead of
|
||||||
:class:`bytes`.
|
:class:`bytes`.
|
||||||
|
|
||||||
|
|
|
@ -744,6 +744,20 @@ getenvironment(PyObject* environment)
|
||||||
"environment can only contain strings");
|
"environment can only contain strings");
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
if (PyUnicode_FindChar(key, '\0', 0, PyUnicode_GET_LENGTH(key), 1) != -1 ||
|
||||||
|
PyUnicode_FindChar(value, '\0', 0, PyUnicode_GET_LENGTH(value), 1) != -1)
|
||||||
|
{
|
||||||
|
PyErr_SetString(PyExc_ValueError, "embedded null character");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
/* Search from index 1 because on Windows starting '=' is allowed for
|
||||||
|
defining hidden environment variables. */
|
||||||
|
if (PyUnicode_GET_LENGTH(key) == 0 ||
|
||||||
|
PyUnicode_FindChar(key, '=', 1, PyUnicode_GET_LENGTH(key), 1) != -1)
|
||||||
|
{
|
||||||
|
PyErr_SetString(PyExc_ValueError, "illegal environment variable name");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
if (totalsize > PY_SSIZE_T_MAX - PyUnicode_GET_LENGTH(key) - 1) {
|
if (totalsize > PY_SSIZE_T_MAX - PyUnicode_GET_LENGTH(key) - 1) {
|
||||||
PyErr_SetString(PyExc_OverflowError, "environment too long");
|
PyErr_SetString(PyExc_OverflowError, "environment too long");
|
||||||
goto error;
|
goto error;
|
||||||
|
@ -830,7 +844,8 @@ _winapi_CreateProcess_impl(PyObject *module, Py_UNICODE *application_name,
|
||||||
PROCESS_INFORMATION pi;
|
PROCESS_INFORMATION pi;
|
||||||
STARTUPINFOW si;
|
STARTUPINFOW si;
|
||||||
PyObject* environment;
|
PyObject* environment;
|
||||||
wchar_t *wenvironment;
|
const wchar_t *wenvironment;
|
||||||
|
Py_ssize_t wenvironment_size;
|
||||||
|
|
||||||
ZeroMemory(&si, sizeof(si));
|
ZeroMemory(&si, sizeof(si));
|
||||||
si.cb = sizeof(si);
|
si.cb = sizeof(si);
|
||||||
|
@ -846,12 +861,13 @@ _winapi_CreateProcess_impl(PyObject *module, Py_UNICODE *application_name,
|
||||||
|
|
||||||
if (env_mapping != Py_None) {
|
if (env_mapping != Py_None) {
|
||||||
environment = getenvironment(env_mapping);
|
environment = getenvironment(env_mapping);
|
||||||
if (! environment)
|
if (environment == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
|
/* contains embedded null characters */
|
||||||
wenvironment = PyUnicode_AsUnicode(environment);
|
wenvironment = PyUnicode_AsUnicode(environment);
|
||||||
if (wenvironment == NULL)
|
if (wenvironment == NULL) {
|
||||||
{
|
Py_DECREF(environment);
|
||||||
Py_XDECREF(environment);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2558,8 +2558,8 @@ _PySequence_BytesToCharpArray(PyObject* self)
|
||||||
array[i] = NULL;
|
array[i] = NULL;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
data = PyBytes_AsString(item);
|
/* check for embedded null bytes */
|
||||||
if (data == NULL) {
|
if (PyBytes_AsStringAndSize(item, &data, NULL) < 0) {
|
||||||
/* NULL terminate before freeing. */
|
/* NULL terminate before freeing. */
|
||||||
array[i] = NULL;
|
array[i] = NULL;
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
Loading…
Reference in New Issue