@@ -124,6 +124,59 @@ def pump_stream(cmdline, name, stream, is_decode, handler):
124124 return finalizer (process )
125125
126126
127+ def _safer_popen_windows (command , shell , env = None , ** kwargs ):
128+ """Call :class:`subprocess.Popen` on Windows but don't include a CWD in the search.
129+ This avoids an untrusted search path condition where a file like ``git.exe`` in a
130+ malicious repository would be run when GitPython operates on the repository. The
131+ process using GitPython may have an untrusted repository's working tree as its
132+ current working directory. Some operations may temporarily change to that directory
133+ before running a subprocess. In addition, while by default GitPython does not run
134+ external commands with a shell, it can be made to do so, in which case the CWD of
135+ the subprocess, which GitPython usually sets to a repository working tree, can
136+ itself be searched automatically by the shell. This wrapper covers all those cases.
137+ :note: This currently works by setting the ``NoDefaultCurrentDirectoryInExePath``
138+ environment variable during subprocess creation. It also takes care of passing
139+ Windows-specific process creation flags, but that is unrelated to path search.
140+ :note: The current implementation contains a race condition on :attr:`os.environ`.
141+ GitPython isn't thread-safe, but a program using it on one thread should ideally
142+ be able to mutate :attr:`os.environ` on another, without unpredictable results.
143+ See comments in https://github.com/gitpython-developers/GitPython/pull/1650.
144+ """
145+ # CREATE_NEW_PROCESS_GROUP is needed for some ways of killing it afterwards. See:
146+ # https://docs.python.org/3/library/subprocess.html#subprocess.Popen.send_signal
147+ # https://docs.python.org/3/library/subprocess.html#subprocess.CREATE_NEW_PROCESS_GROUP
148+ creationflags = subprocess .CREATE_NO_WINDOW | subprocess .CREATE_NEW_PROCESS_GROUP
149+
150+ # When using a shell, the shell is the direct subprocess, so the variable must be
151+ # set in its environment, to affect its search behavior. (The "1" can be any value.)
152+ if shell :
153+ safer_env = {} if env is None else dict (env )
154+ safer_env ["NoDefaultCurrentDirectoryInExePath" ] = "1"
155+ else :
156+ safer_env = env
157+
158+ # When not using a shell, the current process does the search in a CreateProcessW
159+ # API call, so the variable must be set in our environment. With a shell, this is
160+ # unnecessary, in versions where https://github.com/python/cpython/issues/101283 is
161+ # patched. If not, in the rare case the ComSpec environment variable is unset, the
162+ # shell is searched for unsafely. Setting NoDefaultCurrentDirectoryInExePath in all
163+ # cases, as here, is simpler and protects against that. (The "1" can be any value.)
164+ with patch_env ("NoDefaultCurrentDirectoryInExePath" , "1" ):
165+ return Popen (
166+ command ,
167+ shell = shell ,
168+ env = safer_env ,
169+ creationflags = creationflags ,
170+ ** kwargs
171+ )
172+
173+
174+ if os .name == "nt" :
175+ safer_popen = _safer_popen_windows
176+ else :
177+ safer_popen = Popen
178+
179+
127180def dashify (string ):
128181 return string .replace ('_' , '-' )
129182
@@ -144,11 +197,6 @@ def dict_to_slots_and__excluded_are_none(self, d, excluded=()):
144197# value of Windows process creation flag taken from MSDN
145198CREATE_NO_WINDOW = 0x08000000
146199
147- ## CREATE_NEW_PROCESS_GROUP is needed to allow killing it afterwards,
148- # see https://docs.python.org/3/library/subprocess.html#subprocess.Popen.send_signal
149- PROC_CREATIONFLAGS = (CREATE_NO_WINDOW | subprocess .CREATE_NEW_PROCESS_GROUP
150- if is_win else 0 )
151-
152200
153201class Git (LazyMixin ):
154202
@@ -721,19 +769,18 @@ def execute(self, command,
721769 log .debug ("Popen(%s, cwd=%s, universal_newlines=%s, shell=%s, istream=%s)" ,
722770 command , cwd , universal_newlines , shell , istream_ok )
723771 try :
724- proc = Popen (command ,
725- env = env ,
726- cwd = cwd ,
727- bufsize = - 1 ,
728- stdin = istream ,
729- stderr = PIPE ,
730- stdout = stdout_sink ,
731- shell = shell is not None and shell or self .USE_SHELL ,
732- close_fds = is_posix , # unsupported on windows
733- universal_newlines = universal_newlines ,
734- creationflags = PROC_CREATIONFLAGS ,
735- ** subprocess_kwargs
736- )
772+ proc = safer_popen (
773+ command ,
774+ env = env ,
775+ cwd = cwd ,
776+ bufsize = - 1 ,
777+ stdin = istream ,
778+ stderr = PIPE ,
779+ stdout = stdout_sink ,
780+ shell = shell is not None and shell or self .USE_SHELL ,
781+ universal_newlines = universal_newlines ,
782+ ** subprocess_kwargs
783+ )
737784 except cmd_not_found_exception as err :
738785 raise GitCommandNotFound (command , err )
739786
0 commit comments