Skip to content

bpo-30730: Prevent environment variables injection in subprocess on Windows. #2325

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 23, 2017

Conversation

serhiy-storchaka
Copy link
Member

Prevent passing other environment variables and command arguments.

…indows.

Prevent passing other environment variables and command arguments.
PyErr_SetString(PyExc_ValueError, "embedded null character");
goto error;
}
if (PyUnicode_FindChar(key, '=', 0, PyUnicode_GET_LENGTH(key), 1) != -1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This search should start at index 1. On Windows, the equals character is allowed as the first character of an environment variable name. It's used for hidden environment variables such as per-drive working directories, e.g. =E:=E:\working\directory.

Really, Windows Python has a deficiency in that os.environ is built from the CRT's environ, which doesn't include these hidden variables. If a script uses os.environ.copy() or nt.environ.copy() as the basis for the env parameter, then it loses the per-drive working directories (except for the process current directory). For example:

>>> os.chdir(r'E:\working\directory')
>>> os.chdir(r'C:\Temp')
>>> args = ['python', '-c', "import os; print(os.path.abspath('E:test'))"]

>>> r = subprocess.call(args)
E:\working\directory\test

>>> environ = nt.environ.copy()
>>> r = subprocess.call(args, env=environ)
E:\test

>>> environ['=E:'] = r'E:\working\directory'
>>> r = subprocess.call(args, env=environ)
E:\working\directory\test

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Where can I read about this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I know you won't find anything helpful on MSDN. Their article on changing environment variables says that equals cannot be used in the name, without explaining its use as the first character. Also, their article on changing the current directory incorrectly states that SetCurrentDirectory remembers per-drive directories, which it definitely never does.

The Windows API consumes these variables when resolving paths, but it never sets them. That's left to applications, and usually via the C runtime chdir function. Since Python has its own implementation of chdir on Windows, you can see how it sets these variables in win32_wchdir.

@serhiy-storchaka serhiy-storchaka merged commit d174d24 into python:master Jun 23, 2017
@serhiy-storchaka serhiy-storchaka deleted the env-var-injection branch June 23, 2017 16:39
serhiy-storchaka added a commit to serhiy-storchaka/cpython that referenced this pull request Jun 23, 2017
…s on Windows. (pythonGH-2325)

Prevent passing other invalid environment variables and command arguments..
(cherry picked from commit d174d24)
@bedevere-bot
Copy link

GH-2360 is a backport of this pull request to the 3.6 branch.

serhiy-storchaka added a commit to serhiy-storchaka/cpython that referenced this pull request Jun 23, 2017
…s on Windows. (pythonGH-2325)

Prevent passing other invalid environment variables and command arguments..
(cherry picked from commit d174d24)
@bedevere-bot
Copy link

GH-2361 is a backport of this pull request to the 3.5 branch.

serhiy-storchaka added a commit that referenced this pull request Jun 23, 2017
…s on Windows. (GH-2325) (#2360)

Prevent passing other invalid environment variables and command arguments..
(cherry picked from commit d174d24)
serhiy-storchaka added a commit that referenced this pull request Jun 23, 2017
…s on Windows. (GH-2325) (#2361)

Prevent passing other invalid environment variables and command arguments..
(cherry picked from commit d174d24)
serhiy-storchaka added a commit to serhiy-storchaka/cpython that referenced this pull request Jun 24, 2017
…s on Windows. (pythonGH-2325)

Prevent passing other invalid environment variables and command arguments..
(cherry picked from commit d174d24)
@bedevere-bot
Copy link

GH-2372 is a backport of this pull request to the 2.7 branch.

serhiy-storchaka added a commit that referenced this pull request Jun 24, 2017
…s on Windows. (GH-2325) (#2372)

Prevent passing other invalid environment variables and command arguments..
(cherry picked from commit d174d24)
serhiy-storchaka added a commit to serhiy-storchaka/cpython that referenced this pull request Jun 24, 2017
…s on Windows. (pythonGH-2325)

Prevent passing other invalid environment variables and command arguments..
(cherry picked from commit d174d24)
serhiy-storchaka added a commit to serhiy-storchaka/cpython that referenced this pull request Jun 24, 2017
…s on Windows. (pythonGH-2325)

Prevent passing other invalid environment variables and command arguments..
(cherry picked from commit d174d24)
ned-deily pushed a commit to ned-deily/cpython that referenced this pull request Jul 7, 2017
…s on Windows. (pythonGH-2325) (python#2360)

Prevent passing other invalid environment variables and command arguments..
(cherry picked from commit d174d24)
(cherry picked from commit e713575)
larryhastings pushed a commit that referenced this pull request Jul 11, 2017
… 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
ned-deily pushed a commit that referenced this pull request Jul 19, 2017
/* 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@serhiy-storchaka I am curious about this. Is there a particular version of Windows this works on? We copied this code in PyPy, but it is failing tests for putenv("=hidden", "foo")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mattip, the system tracks the working directory on each drive using environment variable names that start with "=", such as "=C:". Python's os.chdir() implements this. Such names are accessible via WinAPI GetEnvironmentStringsW, GetEnvironmentVariableW, and SetEnvironmentVariableW. In most contexts such variables are hidden or filtered out, and using them at the application level isn't officially supported. In particular, CRT environment functions such as putenv and getenv do not support them, and they're filtered out of C [_w]environ, from which Python initializes os.environ.

Here's a manual example:

>>> os.path.abspath('Z:')
'Z:\\'
>>> win32api.SetEnvironmentVariable('=Z:', 'Z:\\test_cwd')
>>> os.path.abspath('Z:')
'Z:\\test_cwd'

Or set the drive working directory in the environment of a child process:

>>> os.path.abspath('Z:')
'Z:\\'
>>> environ = os.environ.copy()
>>> environ['=Z:'] = 'Z:\\test_cwd'
>>> subprocess.call('python -c "import os;print(os.path.abspath(\'Z:\'))"', env=environ)
Z:\test_cwd
0

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick reply. So if I understand correctly while the code (in three places: this one and twice in posix_module) may permit the use of =hidden, it is not guaranteed that the subsequent system call will succeed.

Copy link
Contributor

@eryksun eryksun Jul 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Names that begin with "=" always work with the Windows functions GetEnvironmentVariableW and SetEnvironmentVariableW, and they're inherited fine by child processes. For example:

>>> win32api.SetEnvironmentVariable('=spam', 'eggs')
>>> win32api.GetEnvironmentVariable('=spam')
'eggs'
>>> subprocess.call('python -c "import win32api; print(win32api.GetEnvironmentVariable(\'=spam\'))"')
eggs
0

However, such names are not officially supported. IMO, this is primarily because the C runtime filters them out of its [_w]environ global variable and doesn't support them in environment functions such as putenv(). This in turn is because C and POSIX are tightly linked, and POSIX disallows "=" in environment variable names. Python's os.environ, os.getenv(), and os.putenv() are based on the Windows C runtime (i.e. ucrt), so they have the same limits, and probably should since these are C/POSIX constructs. Maybe one day Python will provide native Windows functionality in the os module that directly accesses the environment block of the process and directly exposes the WinAPI GetEnvironmentVariableW and SetEnvironmentVariableW functions. For now, that's only possible with extensions such as PyWin32 or ctypes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. So I will keep PyPy's putenv consistent with CPython: python allows =hidden as a name, but then the putenv syscall will fail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants