Skip to content

Commit 8b75434

Browse files
authored
Merge pull request #1636 from EliahKagan/cve-2023-40590
Fix CVE-2023-40590
2 parents e19abe7 + 7611cd9 commit 8b75434

File tree

2 files changed

+52
-18
lines changed

2 files changed

+52
-18
lines changed

git/cmd.py

+21-17
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
66
from __future__ import annotations
77
import re
8-
from contextlib import contextmanager
8+
import contextlib
99
import io
1010
import logging
1111
import os
@@ -14,6 +14,7 @@
1414
import subprocess
1515
import threading
1616
from textwrap import dedent
17+
import unittest.mock
1718

1819
from git.compat import (
1920
defenc,
@@ -963,8 +964,11 @@ def execute(
963964
redacted_command,
964965
'"kill_after_timeout" feature is not supported on Windows.',
965966
)
967+
# Only search PATH, not CWD. This must be in the *caller* environment. The "1" can be any value.
968+
patch_caller_env = unittest.mock.patch.dict(os.environ, {"NoDefaultCurrentDirectoryInExePath": "1"})
966969
else:
967970
cmd_not_found_exception = FileNotFoundError # NOQA # exists, flake8 unknown @UndefinedVariable
971+
patch_caller_env = contextlib.nullcontext()
968972
# end handle
969973

970974
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
@@ -980,21 +984,21 @@ def execute(
980984
istream_ok,
981985
)
982986
try:
983-
proc = Popen(
984-
command,
985-
env=env,
986-
cwd=cwd,
987-
bufsize=-1,
988-
stdin=istream or DEVNULL,
989-
stderr=PIPE,
990-
stdout=stdout_sink,
991-
shell=shell is not None and shell or self.USE_SHELL,
992-
close_fds=is_posix, # unsupported on windows
993-
universal_newlines=universal_newlines,
994-
creationflags=PROC_CREATIONFLAGS,
995-
**subprocess_kwargs,
996-
)
997-
987+
with patch_caller_env:
988+
proc = Popen(
989+
command,
990+
env=env,
991+
cwd=cwd,
992+
bufsize=-1,
993+
stdin=istream or DEVNULL,
994+
stderr=PIPE,
995+
stdout=stdout_sink,
996+
shell=shell is not None and shell or self.USE_SHELL,
997+
close_fds=is_posix, # unsupported on windows
998+
universal_newlines=universal_newlines,
999+
creationflags=PROC_CREATIONFLAGS,
1000+
**subprocess_kwargs,
1001+
)
9981002
except cmd_not_found_exception as err:
9991003
raise GitCommandNotFound(redacted_command, err) from err
10001004
else:
@@ -1144,7 +1148,7 @@ def update_environment(self, **kwargs: Any) -> Dict[str, Union[str, None]]:
11441148
del self._environment[key]
11451149
return old_env
11461150

1147-
@contextmanager
1151+
@contextlib.contextmanager
11481152
def custom_environment(self, **kwargs: Any) -> Iterator[None]:
11491153
"""
11501154
A context manager around the above ``update_environment`` method to restore the

test/test_git.py

+31-1
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
#
55
# This module is part of GitPython and is released under
66
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
7+
import contextlib
78
import os
9+
import shutil
810
import subprocess
911
import sys
10-
from tempfile import TemporaryFile
12+
from tempfile import TemporaryDirectory, TemporaryFile
1113
from unittest import mock
1214

1315
from git import Git, refresh, GitCommandError, GitCommandNotFound, Repo, cmd
@@ -20,6 +22,17 @@
2022
from git.compat import is_win
2123

2224

25+
@contextlib.contextmanager
26+
def _chdir(new_dir):
27+
"""Context manager to temporarily change directory. Not reentrant."""
28+
old_dir = os.getcwd()
29+
os.chdir(new_dir)
30+
try:
31+
yield
32+
finally:
33+
os.chdir(old_dir)
34+
35+
2336
class TestGit(TestBase):
2437
@classmethod
2538
def setUpClass(cls):
@@ -75,6 +88,23 @@ def test_it_transforms_kwargs_into_git_command_arguments(self):
7588
def test_it_executes_git_to_shell_and_returns_result(self):
7689
self.assertRegex(self.git.execute(["git", "version"]), r"^git version [\d\.]{2}.*$")
7790

91+
def test_it_executes_git_not_from_cwd(self):
92+
with TemporaryDirectory() as tmpdir:
93+
if is_win:
94+
# Copy an actual binary executable that is not git.
95+
other_exe_path = os.path.join(os.getenv("WINDIR"), "system32", "hostname.exe")
96+
impostor_path = os.path.join(tmpdir, "git.exe")
97+
shutil.copy(other_exe_path, impostor_path)
98+
else:
99+
# Create a shell script that doesn't do anything.
100+
impostor_path = os.path.join(tmpdir, "git")
101+
with open(impostor_path, mode="w", encoding="utf-8") as file:
102+
print("#!/bin/sh", file=file)
103+
os.chmod(impostor_path, 0o755)
104+
105+
with _chdir(tmpdir):
106+
self.assertRegex(self.git.execute(["git", "version"]), r"^git version\b")
107+
78108
def test_it_accepts_stdin(self):
79109
filename = fixture_path("cat_file_blob")
80110
with open(filename, "r") as fh:

0 commit comments

Comments
 (0)