summaryrefslogtreecommitdiff
path: root/git
diff options
context:
space:
mode:
authorSteve Kowalik <steven@wedontsleep.org>2022-12-20 17:05:50 +1100
committerSantos Gallegos <stsewd@proton.me>2022-12-23 16:16:21 -0500
commit2625ed9fc074091c531c27ffcba7902771130261 (patch)
tree0f3589b06f2b63851addf126b245073cad7eacbf /git
parent787359d80d80225095567340aa5e7ec01847fa9a (diff)
downloadgitpython-2625ed9fc074091c531c27ffcba7902771130261.tar.gz
Forbid unsafe protocol URLs in Repo.clone{,_from}()
Since the URL is passed directly to git clone, and the remote-ext helper will happily execute shell commands, so by default disallow URLs that contain a "::" unless a new unsafe_protocols kwarg is passed. (CVE-2022-24439) Fixes #1515
Diffstat (limited to 'git')
-rw-r--r--git/exc.py4
-rw-r--r--git/repo/base.py31
2 files changed, 34 insertions, 1 deletions
diff --git a/git/exc.py b/git/exc.py
index 22fcde0d..b696d792 100644
--- a/git/exc.py
+++ b/git/exc.py
@@ -37,6 +37,10 @@ class NoSuchPathError(GitError, OSError):
"""Thrown if a path could not be access by the system."""
+class UnsafeOptionsUsedError(GitError):
+ """Thrown if unsafe protocols or options are passed without overridding."""
+
+
class CommandError(GitError):
"""Base class for exceptions thrown at every stage of `Popen()` execution.
diff --git a/git/repo/base.py b/git/repo/base.py
index 49a3d5a1..35ff68b0 100644
--- a/git/repo/base.py
+++ b/git/repo/base.py
@@ -21,7 +21,12 @@ from git.compat import (
)
from git.config import GitConfigParser
from git.db import GitCmdObjectDB
-from git.exc import InvalidGitRepositoryError, NoSuchPathError, GitCommandError
+from git.exc import (
+ GitCommandError,
+ InvalidGitRepositoryError,
+ NoSuchPathError,
+ UnsafeOptionsUsedError,
+)
from git.index import IndexFile
from git.objects import Submodule, RootModule, Commit
from git.refs import HEAD, Head, Reference, TagReference
@@ -128,6 +133,7 @@ class Repo(object):
re_envvars = re.compile(r"(\$(\{\s?)?[a-zA-Z_]\w*(\}\s?)?|%\s?[a-zA-Z_]\w*\s?%)")
re_author_committer_start = re.compile(r"^(author|committer)")
re_tab_full_line = re.compile(r"^\t(.*)$")
+ re_config_protocol_option = re.compile(r"-[-]?c(|onfig)\s+protocol\.", re.I)
# invariants
# represents the configuration level of a configuration file
@@ -1215,11 +1221,27 @@ class Repo(object):
# END handle remote repo
return repo
+ @classmethod
+ def unsafe_options(
+ cls,
+ url: str,
+ multi_options: Optional[List[str]] = None,
+ ) -> bool:
+ if "ext::" in url:
+ return True
+ if multi_options is not None:
+ if any(["--upload-pack" in m for m in multi_options]):
+ return True
+ if any([re.match(cls.re_config_protocol_option, m) for m in multi_options]):
+ return True
+ return False
+
def clone(
self,
path: PathLike,
progress: Optional[Callable] = None,
multi_options: Optional[List[str]] = None,
+ unsafe_protocols: bool = False,
**kwargs: Any,
) -> "Repo":
"""Create a clone from this repository.
@@ -1230,12 +1252,15 @@ class Repo(object):
option per list item which is passed exactly as specified to clone.
For example ['--config core.filemode=false', '--config core.ignorecase',
'--recurse-submodule=repo1_path', '--recurse-submodule=repo2_path']
+ :param unsafe_protocols: Allow unsafe protocols to be used, like ext
:param kwargs:
* odbt = ObjectDatabase Type, allowing to determine the object database
implementation used by the returned Repo instance
* All remaining keyword arguments are given to the git-clone command
:return: ``git.Repo`` (the newly cloned repo)"""
+ if not unsafe_protocols and self.unsafe_options(path, multi_options):
+ raise UnsafeOptionsUsedError(f"{path} requires unsafe_protocols flag")
return self._clone(
self.git,
self.common_dir,
@@ -1254,6 +1279,7 @@ class Repo(object):
progress: Optional[Callable] = None,
env: Optional[Mapping[str, str]] = None,
multi_options: Optional[List[str]] = None,
+ unsafe_protocols: bool = False,
**kwargs: Any,
) -> "Repo":
"""Create a clone from the given URL
@@ -1268,11 +1294,14 @@ class Repo(object):
If you want to unset some variable, consider providing empty string
as its value.
:param multi_options: See ``clone`` method
+ :param unsafe_protocols: Allow unsafe protocols to be used, like ext
:param kwargs: see the ``clone`` method
:return: Repo instance pointing to the cloned directory"""
git = cls.GitCommandWrapperType(os.getcwd())
if env is not None:
git.update_environment(**env)
+ if not unsafe_protocols and cls.unsafe_options(url, multi_options):
+ raise UnsafeOptionsUsedError(f"{url} requires unsafe_protocols flag")
return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs)
def archive(