@@ -124,6 +124,59 @@ def pump_stream(cmdline, name, stream, is_decode, handler):
124
124
return finalizer (process )
125
125
126
126
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
+
127
180
def dashify (string ):
128
181
return string .replace ('_' , '-' )
129
182
@@ -144,11 +197,6 @@ def dict_to_slots_and__excluded_are_none(self, d, excluded=()):
144
197
# value of Windows process creation flag taken from MSDN
145
198
CREATE_NO_WINDOW = 0x08000000
146
199
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
-
152
200
153
201
class Git (LazyMixin ):
154
202
@@ -721,19 +769,18 @@ def execute(self, command,
721
769
log .debug ("Popen(%s, cwd=%s, universal_newlines=%s, shell=%s, istream=%s)" ,
722
770
command , cwd , universal_newlines , shell , istream_ok )
723
771
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
+ )
737
784
except cmd_not_found_exception as err :
738
785
raise GitCommandNotFound (command , err )
739
786
0 commit comments