55# the BSD License: http://www.opensource.org/licenses/bsd-license.php
66
77import contextlib
8+ import re
9+
810import io
911import logging
1012import os
@@ -130,6 +132,59 @@ def pump_stream(cmdline, name, stream, is_decode, handler):
130132 return finalizer (process )
131133
132134
135+ def _safer_popen_windows (command , shell , env = None , ** kwargs ):
136+ """Call :class:`subprocess.Popen` on Windows but don't include a CWD in the search.
137+ This avoids an untrusted search path condition where a file like ``git.exe`` in a
138+ malicious repository would be run when GitPython operates on the repository. The
139+ process using GitPython may have an untrusted repository's working tree as its
140+ current working directory. Some operations may temporarily change to that directory
141+ before running a subprocess. In addition, while by default GitPython does not run
142+ external commands with a shell, it can be made to do so, in which case the CWD of
143+ the subprocess, which GitPython usually sets to a repository working tree, can
144+ itself be searched automatically by the shell. This wrapper covers all those cases.
145+ :note: This currently works by setting the ``NoDefaultCurrentDirectoryInExePath``
146+ environment variable during subprocess creation. It also takes care of passing
147+ Windows-specific process creation flags, but that is unrelated to path search.
148+ :note: The current implementation contains a race condition on :attr:`os.environ`.
149+ GitPython isn't thread-safe, but a program using it on one thread should ideally
150+ be able to mutate :attr:`os.environ` on another, without unpredictable results.
151+ See comments in https://github.com/gitpython-developers/GitPython/pull/1650.
152+ """
153+ # CREATE_NEW_PROCESS_GROUP is needed for some ways of killing it afterwards. See:
154+ # https://docs.python.org/3/library/subprocess.html#subprocess.Popen.send_signal
155+ # https://docs.python.org/3/library/subprocess.html#subprocess.CREATE_NEW_PROCESS_GROUP
156+ creationflags = subprocess .CREATE_NO_WINDOW | subprocess .CREATE_NEW_PROCESS_GROUP
157+
158+ # When using a shell, the shell is the direct subprocess, so the variable must be
159+ # set in its environment, to affect its search behavior. (The "1" can be any value.)
160+ if shell :
161+ safer_env = {} if env is None else dict (env )
162+ safer_env ["NoDefaultCurrentDirectoryInExePath" ] = "1"
163+ else :
164+ safer_env = env
165+
166+ # When not using a shell, the current process does the search in a CreateProcessW
167+ # API call, so the variable must be set in our environment. With a shell, this is
168+ # unnecessary, in versions where https://github.com/python/cpython/issues/101283 is
169+ # patched. If not, in the rare case the ComSpec environment variable is unset, the
170+ # shell is searched for unsafely. Setting NoDefaultCurrentDirectoryInExePath in all
171+ # cases, as here, is simpler and protects against that. (The "1" can be any value.)
172+ with patch_env ("NoDefaultCurrentDirectoryInExePath" , "1" ):
173+ return Popen (
174+ command ,
175+ shell = shell ,
176+ env = safer_env ,
177+ creationflags = creationflags ,
178+ ** kwargs
179+ )
180+
181+
182+ if os .name == "nt" :
183+ safer_popen = _safer_popen_windows
184+ else :
185+ safer_popen = Popen
186+
187+
133188def dashify (string ):
134189 return string .replace ('_' , '-' )
135190
@@ -150,11 +205,6 @@ def dict_to_slots_and__excluded_are_none(self, d, excluded=()):
150205# value of Windows process creation flag taken from MSDN
151206CREATE_NO_WINDOW = 0x08000000
152207
153- ## CREATE_NEW_PROCESS_GROUP is needed to allow killing it afterwards,
154- # see https://docs.python.org/3/library/subprocess.html#subprocess.Popen.send_signal
155- PROC_CREATIONFLAGS = (CREATE_NO_WINDOW | subprocess .CREATE_NEW_PROCESS_GROUP
156- if is_win else 0 )
157-
158208
159209class Git (LazyMixin ):
160210
@@ -771,21 +821,18 @@ def execute(self, command,
771821 log .debug ("Popen(%s, cwd=%s, universal_newlines=%s, shell=%s, istream=%s)" ,
772822 command , cwd , universal_newlines , shell , istream_ok )
773823 try :
774- with patch_caller_env :
775- proc = Popen (
776- command ,
777- env = env ,
778- cwd = cwd ,
779- bufsize = - 1 ,
780- stdin = istream ,
781- stderr = PIPE ,
782- stdout = stdout_sink ,
783- shell = shell is not None and shell or self .USE_SHELL ,
784- close_fds = is_posix , # unsupported on windows
785- universal_newlines = universal_newlines ,
786- creationflags = PROC_CREATIONFLAGS ,
787- ** subprocess_kwargs
788- )
824+ proc = safer_popen (
825+ command ,
826+ env = env ,
827+ cwd = cwd ,
828+ bufsize = - 1 ,
829+ stdin = istream ,
830+ stderr = PIPE ,
831+ stdout = stdout_sink ,
832+ shell = shell is not None and shell or self .USE_SHELL ,
833+ universal_newlines = universal_newlines ,
834+ ** subprocess_kwargs
835+ )
789836 except cmd_not_found_exception as err :
790837 raise GitCommandNotFound (command , err )
791838
0 commit comments