Skip to content

Commit 23751ed

Browse files
authored
gh-101283: Improved fallback logic for subprocess with shell=True on Windows (GH-101286)
1 parent de3669e commit 23751ed

File tree

3 files changed

+58
-1
lines changed

3 files changed

+58
-1
lines changed

Doc/library/subprocess.rst

+40
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,14 @@ underlying :class:`Popen` interface can be used directly.
111111
Added the *text* parameter, as a more understandable alias of *universal_newlines*.
112112
Added the *capture_output* parameter.
113113

114+
.. versionchanged:: 3.11.2
115+
116+
Changed Windows shell search order for ``shell=True``. The current
117+
directory and ``%PATH%`` are replaced with ``%COMSPEC%`` and
118+
``%SystemRoot%\System32\cmd.exe``. As a result, dropping a
119+
malicious program named ``cmd.exe`` into a current directory no
120+
longer works.
121+
114122
.. class:: CompletedProcess
115123

116124
The return value from :func:`run`, representing a process that has finished.
@@ -487,6 +495,14 @@ functions.
487495
*executable* parameter accepts a bytes and :term:`path-like object`
488496
on Windows.
489497

498+
.. versionchanged:: 3.11.2
499+
500+
Changed Windows shell search order for ``shell=True``. The current
501+
directory and ``%PATH%`` are replaced with ``%COMSPEC%`` and
502+
``%SystemRoot%\System32\cmd.exe``. As a result, dropping a
503+
malicious program named ``cmd.exe`` into a current directory no
504+
longer works.
505+
490506
*stdin*, *stdout* and *stderr* specify the executed program's standard input,
491507
standard output and standard error file handles, respectively. Valid values
492508
are ``None``, :data:`PIPE`, :data:`DEVNULL`, an existing file descriptor (a
@@ -1158,6 +1174,14 @@ calls these functions.
11581174
.. versionchanged:: 3.3
11591175
*timeout* was added.
11601176

1177+
.. versionchanged:: 3.11.2
1178+
1179+
Changed Windows shell search order for ``shell=True``. The current
1180+
directory and ``%PATH%`` are replaced with ``%COMSPEC%`` and
1181+
``%SystemRoot%\System32\cmd.exe``. As a result, dropping a
1182+
malicious program named ``cmd.exe`` into a current directory no
1183+
longer works.
1184+
11611185
.. function:: check_call(args, *, stdin=None, stdout=None, stderr=None, \
11621186
shell=False, cwd=None, timeout=None, \
11631187
**other_popen_kwargs)
@@ -1190,6 +1214,14 @@ calls these functions.
11901214
.. versionchanged:: 3.3
11911215
*timeout* was added.
11921216

1217+
.. versionchanged:: 3.11.2
1218+
1219+
Changed Windows shell search order for ``shell=True``. The current
1220+
directory and ``%PATH%`` are replaced with ``%COMSPEC%`` and
1221+
``%SystemRoot%\System32\cmd.exe``. As a result, dropping a
1222+
malicious program named ``cmd.exe`` into a current directory no
1223+
longer works.
1224+
11931225

11941226
.. function:: check_output(args, *, stdin=None, stderr=None, shell=False, \
11951227
cwd=None, encoding=None, errors=None, \
@@ -1245,6 +1277,14 @@ calls these functions.
12451277
.. versionadded:: 3.7
12461278
*text* was added as a more readable alias for *universal_newlines*.
12471279

1280+
.. versionchanged:: 3.11.2
1281+
1282+
Changed Windows shell search order for ``shell=True``. The current
1283+
directory and ``%PATH%`` are replaced with ``%COMSPEC%`` and
1284+
``%SystemRoot%\System32\cmd.exe``. As a result, dropping a
1285+
malicious program named ``cmd.exe`` into a current directory no
1286+
longer works.
1287+
12481288

12491289
.. _subprocess-replacements:
12501290

Lib/subprocess.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -1480,7 +1480,21 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
14801480
if shell:
14811481
startupinfo.dwFlags |= _winapi.STARTF_USESHOWWINDOW
14821482
startupinfo.wShowWindow = _winapi.SW_HIDE
1483-
comspec = os.environ.get("COMSPEC", "cmd.exe")
1483+
if not executable:
1484+
# gh-101283: without a fully-qualified path, before Windows
1485+
# checks the system directories, it first looks in the
1486+
# application directory, and also the current directory if
1487+
# NeedCurrentDirectoryForExePathW(ExeName) is true, so try
1488+
# to avoid executing unqualified "cmd.exe".
1489+
comspec = os.environ.get('ComSpec')
1490+
if not comspec:
1491+
system_root = os.environ.get('SystemRoot', '')
1492+
comspec = os.path.join(system_root, 'System32', 'cmd.exe')
1493+
if not os.path.isabs(comspec):
1494+
raise FileNotFoundError('shell not found: neither %ComSpec% nor %SystemRoot% is set')
1495+
if os.path.isabs(comspec):
1496+
executable = comspec
1497+
14841498
args = '{} /c "{}"'.format (comspec, args)
14851499

14861500
if cwd is not None:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:class:`subprocess.Popen` now uses a safer approach to find
2+
``cmd.exe`` when launching with ``shell=True``. Patch by Eryk Sun,
3+
based on a patch by Oleg Iarygin.

0 commit comments

Comments
 (0)