| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent c551e91 commit 3eb7c2a
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -29,8 +29,8 @@ | |||
| 29 | 29 | cygpath, | |
| 30 | 30 | expand_path, | |
| 31 | 31 | is_cygwin_git, | |
| 32 | + patch_env, | ||
| 32 | 33 | remove_password_if_present, | |
| 33 | - safer_popen, | ||
| 34 | 34 | stream_copy, | |
| 35 | 35 | ) | |
| 36 | 36 | ||
@@ -46,6 +46,7 @@ | |||
| 46 | 46 | Iterator, | |
| 47 | 47 | List, | |
| 48 | 48 | Mapping, | |
| 49 | + Optional, | ||
| 49 | 50 | Sequence, | |
| 50 | 51 | TYPE_CHECKING, | |
| 51 | 52 | TextIO, | |
@@ -102,7 +103,7 @@ def handle_process_output( | |||
| 102 | 103 | Callable[[bytes, "Repo", "DiffIndex"], None], | |
| 103 | 104 | ], | |
| 104 | 105 | stderr_handler: Union[None, Callable[[AnyStr], None], Callable[[List[AnyStr]], None]], | |
| 105 | - finalizer: Union[None, Callable[[Union[subprocess.Popen, "Git.AutoInterrupt"]], None]] = None, | ||
| 106 | + finalizer: Union[None, Callable[[Union[Popen, "Git.AutoInterrupt"]], None]] = None, | ||
| 106 | 107 | decode_streams: bool = True, | |
| 107 | 108 | kill_after_timeout: Union[None, float] = None, | |
| 108 | 109 | ) -> None: | |
@@ -207,6 +208,68 @@ def pump_stream( | |||
| 207 | 208 | finalizer(process) | |
| 208 | 209 | ||
| 209 | 210 | ||
| 211 | + def _safer_popen_windows( | ||
| 212 | + command: Union[str, Sequence[Any]], | ||
| 213 | + *, | ||
| 214 | + shell: bool = False, | ||
| 215 | + env: Optional[Mapping[str, str]] = None, | ||
| 216 | + **kwargs: Any, | ||
| 217 | + ) -> Popen: | ||
| 218 | + """Call :class:`subprocess.Popen` on Windows but don't include a CWD in the search. | ||
| 219 | + | ||
| 220 | + This avoids an untrusted search path condition where a file like ``git.exe`` in a | ||
| 221 | + malicious repository would be run when GitPython operates on the repository. The | ||
| 222 | + process using GitPython may have an untrusted repository's working tree as its | ||
| 223 | + current working directory. Some operations may temporarily change to that directory | ||
| 224 | + before running a subprocess. In addition, while by default GitPython does not run | ||
| 225 | + external commands with a shell, it can be made to do so, in which case the CWD of | ||
| 226 | + the subprocess, which GitPython usually sets to a repository working tree, can | ||
| 227 | + itself be searched automatically by the shell. This wrapper covers all those cases. | ||
| 228 | + | ||
| 229 | + :note: This currently works by setting the ``NoDefaultCurrentDirectoryInExePath`` | ||
| 230 | + environment variable during subprocess creation. It also takes care of passing | ||
| 231 | + Windows-specific process creation flags, but that is unrelated to path search. | ||
| 232 | + | ||
| 233 | + :note: The current implementation contains a race condition on :attr:`os.environ`. | ||
| 234 | + GitPython isn't thread-safe, but a program using it on one thread should ideally | ||
| 235 | + be able to mutate :attr:`os.environ` on another, without unpredictable results. | ||
| 236 | + See comments in https://github.com/gitpython-developers/GitPython/pull/1650. | ||
| 237 | + """ | ||
| 238 | + # CREATE_NEW_PROCESS_GROUP is needed for some ways of killing it afterwards. See: | ||
| 239 | + # https://docs.python.org/3/library/subprocess.html#subprocess.Popen.send_signal | ||
| 240 | + # https://docs.python.org/3/library/subprocess.html#subprocess.CREATE_NEW_PROCESS_GROUP | ||
| 241 | + creationflags = subprocess.CREATE_NO_WINDOW | subprocess.CREATE_NEW_PROCESS_GROUP | ||
| 242 | + | ||
| 243 | + # When using a shell, the shell is the direct subprocess, so the variable must be | ||
| 244 | + # set in its environment, to affect its search behavior. (The "1" can be any value.) | ||
| 245 | + if shell: | ||
| 246 | + safer_env = {} if env is None else dict(env) | ||
| 247 | + safer_env["NoDefaultCurrentDirectoryInExePath"] = "1" | ||
| 248 | + else: | ||
| 249 | + safer_env = env | ||
| 250 | + | ||
| 251 | + # When not using a shell, the current process does the search in a CreateProcessW | ||
| 252 | + # API call, so the variable must be set in our environment. With a shell, this is | ||
| 253 | + # unnecessary, in versions where https://github.com/python/cpython/issues/101283 is | ||
| 254 | + # patched. If not, in the rare case the ComSpec environment variable is unset, the | ||
| 255 | + # shell is searched for unsafely. Setting NoDefaultCurrentDirectoryInExePath in all | ||
| 256 | + # cases, as here, is simpler and protects against that. (The "1" can be any value.) | ||
| 257 | + with patch_env("NoDefaultCurrentDirectoryInExePath", "1"): | ||
| 258 | + return Popen( | ||
| 259 | + command, | ||
| 260 | + shell=shell, | ||
| 261 | + env=safer_env, | ||
| 262 | + creationflags=creationflags, | ||
| 263 | + **kwargs, | ||
| 264 | + ) | ||
| 265 | + | ||
| 266 | + | ||
| 267 | + if os.name == "nt": | ||
| 268 | + safer_popen = _safer_popen_windows | ||
| 269 | + else: | ||
| 270 | + safer_popen = Popen | ||
| 271 | + | ||
| 272 | + | ||
| 210 | 273 | def dashify(string: str) -> str: | |
| 211 | 274 | return string.replace("_", "-") | |
| 212 | 275 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -18,15 +18,15 @@ | |||
| 18 | 18 | ) | |
| 19 | 19 | import subprocess | |
| 20 | 20 | ||
| 21 | - from git.cmd import handle_process_output | ||
| 21 | + from git.cmd import handle_process_output, safer_popen | ||
| 22 | 22 | from git.compat import defenc, force_bytes, force_text, safe_decode | |
| 23 | 23 | from git.exc import HookExecutionError, UnmergedEntriesError | |
| 24 | 24 | from git.objects.fun import ( | |
| 25 | 25 | traverse_tree_recursive, | |
| 26 | 26 | traverse_trees_recursive, | |
| 27 | 27 | tree_to_stream, | |
| 28 | 28 | ) | |
| 29 | - from git.util import IndexFileSHA1Writer, finalize_process, safer_popen | ||
| 29 | + from git.util import IndexFileSHA1Writer, finalize_process | ||
| 30 | 30 | from gitdb.base import IStream | |
| 31 | 31 | from gitdb.typ import str_tree_type | |
| 32 | 32 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -33,7 +33,6 @@ | |||
| 33 | 33 | IO, | |
| 34 | 34 | Iterator, | |
| 35 | 35 | List, | |
| 36 | - Mapping, | ||
| 37 | 36 | Optional, | |
| 38 | 37 | Pattern, | |
| 39 | 38 | Sequence, | |
@@ -536,67 +535,6 @@ def remove_password_if_present(cmdline: Sequence[str]) -> List[str]: | |||
| 536 | 535 | return new_cmdline | |
| 537 | 536 | ||
| 538 | 537 | ||
| 539 | - def _safer_popen_windows( | ||
| 540 | - command: Union[str, Sequence[Any]], | ||
| 541 | - *, | ||
| 542 | - shell: bool = False, | ||
| 543 | - env: Optional[Mapping[str, str]] = None, | ||
| 544 | - **kwargs: Any, | ||
| 545 | - ) -> subprocess.Popen: | ||
| 546 | - """Call :class:`subprocess.Popen` on Windows but don't include a CWD in the search. | ||
| 547 | - | ||
| 548 | - This avoids an untrusted search path condition where a file like ``git.exe`` in a | ||
| 549 | - malicious repository would be run when GitPython operates on the repository. The | ||
| 550 | - process using GitPython may have an untrusted repository's working tree as its | ||
| 551 | - current working directory. Some operations may temporarily change to that directory | ||
| 552 | - before running a subprocess. In addition, while by default GitPython does not run | ||
| 553 | - external commands with a shell, it can be made to do so, in which case the CWD of | ||
| 554 | - the subprocess, which GitPython usually sets to a repository working tree, can | ||
| 555 | - itself be searched automatically by the shell. This wrapper covers all those cases. | ||
| 556 | - | ||
| 557 | - :note: This currently works by setting the ``NoDefaultCurrentDirectoryInExePath`` | ||
| 558 | - environment variable during subprocess creation. It also takes care of passing | ||
| 559 | - Windows-specific process creation flags, but that is unrelated to path search. | ||
| 560 | - | ||
| 561 | - :note: The current implementation contains a race condition on :attr:`os.environ`. | ||
| 562 | - GitPython isn't thread-safe, but a program using it on one thread should ideally | ||
| 563 | - be able to mutate :attr:`os.environ` on another, without unpredictable results. | ||
| 564 | - See comments in https://github.com/gitpython-developers/GitPython/pull/1650. | ||
| 565 | - """ | ||
| 566 | - # CREATE_NEW_PROCESS_GROUP is needed for some ways of killing it afterwards. See: | ||
| 567 | - # https://docs.python.org/3/library/subprocess.html#subprocess.Popen.send_signal | ||
| 568 | - # https://docs.python.org/3/library/subprocess.html#subprocess.CREATE_NEW_PROCESS_GROUP | ||
| 569 | - creationflags = subprocess.CREATE_NO_WINDOW | subprocess.CREATE_NEW_PROCESS_GROUP | ||
| 570 | - | ||
| 571 | - # When using a shell, the shell is the direct subprocess, so the variable must be | ||
| 572 | - # set in its environment, to affect its search behavior. (The "1" can be any value.) | ||
| 573 | - if shell: | ||
| 574 | - safer_env = {} if env is None else dict(env) | ||
| 575 | - safer_env["NoDefaultCurrentDirectoryInExePath"] = "1" | ||
| 576 | - else: | ||
| 577 | - safer_env = env | ||
| 578 | - | ||
| 579 | - # When not using a shell, the current process does the search in a CreateProcessW | ||
| 580 | - # API call, so the variable must be set in our environment. With a shell, this is | ||
| 581 | - # unnecessary, in versions where https://github.com/python/cpython/issues/101283 is | ||
| 582 | - # patched. If not, in the rare case the ComSpec environment variable is unset, the | ||
| 583 | - # shell is searched for unsafely. Setting NoDefaultCurrentDirectoryInExePath in all | ||
| 584 | - # cases, as here, is simpler and protects against that. (The "1" can be any value.) | ||
| 585 | - with patch_env("NoDefaultCurrentDirectoryInExePath", "1"): | ||
| 586 | - return subprocess.Popen( | ||
| 587 | - command, | ||
| 588 | - shell=shell, | ||
| 589 | - env=safer_env, | ||
| 590 | - creationflags=creationflags, | ||
| 591 | - **kwargs, | ||
| 592 | - ) | ||
| 593 | - | ||
| 594 | - | ||
| 595 | - if os.name == "nt": | ||
| 596 | - safer_popen = _safer_popen_windows | ||
| 597 | - else: | ||
| 598 | - safer_popen = subprocess.Popen | ||
| 599 | - | ||
| 600 | 538 | # } END utilities | |
| 601 | 539 | ||
| 602 | 540 | # { Classes | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,7 +25,7 @@ | |||
| 25 | 25 | import ddt | |
| 26 | 26 | ||
| 27 | 27 | from git import Git, refresh, GitCommandError, GitCommandNotFound, Repo, cmd | |
| 28 | - from git.util import cwd, finalize_process, safer_popen | ||
| 28 | + from git.util import cwd, finalize_process | ||
| 29 | 29 | from test.lib import TestBase, fixture_path, with_rw_directory | |
| 30 | 30 | ||
| 31 | 31 | ||
@@ -114,7 +114,6 @@ def test_it_transforms_kwargs_into_git_command_arguments(self): | |||
| 114 | 114 | ||
| 115 | 115 | def _do_shell_combo(self, value_in_call, value_from_class): | |
| 116 | 116 | with mock.patch.object(Git, "USE_SHELL", value_from_class): | |
| 117 | - # git.cmd gets Popen via a "from" import, so patch it there. | ||
| 118 | 117 | with mock.patch.object(cmd, "safer_popen", wraps=cmd.safer_popen) as mock_safer_popen: | |
| 119 | 118 | # Use a command with no arguments (besides the program name), so it runs | |
| 120 | 119 | # with or without a shell, on all OSes, with the same effect. | |
@@ -389,7 +388,7 @@ def test_environment(self, rw_dir): | |||
| 389 | 388 | self.assertIn("FOO", str(err)) | |
| 390 | 389 | ||
| 391 | 390 | def test_handle_process_output(self): | |
| 392 | - from git.cmd import handle_process_output | ||
| 391 | + from git.cmd import handle_process_output, safer_popen | ||
| 393 | 392 | ||
| 394 | 393 | line_count = 5002 | |
| 395 | 394 | count = [None, 0, 0] | |
| Back | FazBrowse Home | New Git URL |
0 commit comments