Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit2625ed9

Browse files
s-t-e-v-e-n-kstsewd
authored andcommitted
Forbid unsafe protocol URLs in Repo.clone{,_from}()
Since the URL is passed directly to git clone, and the remote-ext helperwill happily execute shell commands, so by default disallow URLs thatcontain a "::" unless a new unsafe_protocols kwarg is passed.(CVE-2022-24439)Fixes#1515
1 parent787359d commit2625ed9

File tree

3 files changed

+70
-1
lines changed

3 files changed

+70
-1
lines changed

‎git/exc.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ class NoSuchPathError(GitError, OSError):
3737
"""Thrown if a path could not be access by the system."""
3838

3939

40+
classUnsafeOptionsUsedError(GitError):
41+
"""Thrown if unsafe protocols or options are passed without overridding."""
42+
43+
4044
classCommandError(GitError):
4145
"""Base class for exceptions thrown at every stage of `Popen()` execution.
4246

‎git/repo/base.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@
2121
)
2222
fromgit.configimportGitConfigParser
2323
fromgit.dbimportGitCmdObjectDB
24-
fromgit.excimportInvalidGitRepositoryError,NoSuchPathError,GitCommandError
24+
fromgit.excimport (
25+
GitCommandError,
26+
InvalidGitRepositoryError,
27+
NoSuchPathError,
28+
UnsafeOptionsUsedError,
29+
)
2530
fromgit.indeximportIndexFile
2631
fromgit.objectsimportSubmodule,RootModule,Commit
2732
fromgit.refsimportHEAD,Head,Reference,TagReference
@@ -128,6 +133,7 @@ class Repo(object):
128133
re_envvars=re.compile(r"(\$(\{\s?)?[a-zA-Z_]\w*(\}\s?)?|%\s?[a-zA-Z_]\w*\s?%)")
129134
re_author_committer_start=re.compile(r"^(author|committer)")
130135
re_tab_full_line=re.compile(r"^\t(.*)$")
136+
re_config_protocol_option=re.compile(r"-[-]?c(|onfig)\s+protocol\.",re.I)
131137

132138
# invariants
133139
# represents the configuration level of a configuration file
@@ -1215,11 +1221,27 @@ def _clone(
12151221
# END handle remote repo
12161222
returnrepo
12171223

1224+
@classmethod
1225+
defunsafe_options(
1226+
cls,
1227+
url:str,
1228+
multi_options:Optional[List[str]]=None,
1229+
)->bool:
1230+
if"ext::"inurl:
1231+
returnTrue
1232+
ifmulti_optionsisnotNone:
1233+
ifany(["--upload-pack"inmforminmulti_options]):
1234+
returnTrue
1235+
ifany([re.match(cls.re_config_protocol_option,m)forminmulti_options]):
1236+
returnTrue
1237+
returnFalse
1238+
12181239
defclone(
12191240
self,
12201241
path:PathLike,
12211242
progress:Optional[Callable]=None,
12221243
multi_options:Optional[List[str]]=None,
1244+
unsafe_protocols:bool=False,
12231245
**kwargs:Any,
12241246
)->"Repo":
12251247
"""Create a clone from this repository.
@@ -1230,12 +1252,15 @@ def clone(
12301252
option per list item which is passed exactly as specified to clone.
12311253
For example ['--config core.filemode=false', '--config core.ignorecase',
12321254
'--recurse-submodule=repo1_path', '--recurse-submodule=repo2_path']
1255+
:param unsafe_protocols: Allow unsafe protocols to be used, like ext
12331256
:param kwargs:
12341257
* odbt = ObjectDatabase Type, allowing to determine the object database
12351258
implementation used by the returned Repo instance
12361259
* All remaining keyword arguments are given to the git-clone command
12371260
12381261
:return: ``git.Repo`` (the newly cloned repo)"""
1262+
ifnotunsafe_protocolsandself.unsafe_options(path,multi_options):
1263+
raiseUnsafeOptionsUsedError(f"{path} requires unsafe_protocols flag")
12391264
returnself._clone(
12401265
self.git,
12411266
self.common_dir,
@@ -1254,6 +1279,7 @@ def clone_from(
12541279
progress:Optional[Callable]=None,
12551280
env:Optional[Mapping[str,str]]=None,
12561281
multi_options:Optional[List[str]]=None,
1282+
unsafe_protocols:bool=False,
12571283
**kwargs:Any,
12581284
)->"Repo":
12591285
"""Create a clone from the given URL
@@ -1268,11 +1294,14 @@ def clone_from(
12681294
If you want to unset some variable, consider providing empty string
12691295
as its value.
12701296
:param multi_options: See ``clone`` method
1297+
:param unsafe_protocols: Allow unsafe protocols to be used, like ext
12711298
:param kwargs: see the ``clone`` method
12721299
:return: Repo instance pointing to the cloned directory"""
12731300
git=cls.GitCommandWrapperType(os.getcwd())
12741301
ifenvisnotNone:
12751302
git.update_environment(**env)
1303+
ifnotunsafe_protocolsandcls.unsafe_options(url,multi_options):
1304+
raiseUnsafeOptionsUsedError(f"{url} requires unsafe_protocols flag")
12761305
returncls._clone(git,url,to_path,GitCmdObjectDB,progress,multi_options,**kwargs)
12771306

12781307
defarchive(

‎test/test_repo.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
importpickle
1414
importsys
1515
importtempfile
16+
importuuid
1617
fromunittestimportmock,skipIf,SkipTest
1718

1819
importpytest
@@ -37,6 +38,7 @@
3738
)
3839
fromgit.excimport (
3940
BadObject,
41+
UnsafeOptionsUsedError,
4042
)
4143
fromgit.repo.funimporttouch
4244
fromtest.libimportTestBase,with_rw_repo,fixture
@@ -263,6 +265,40 @@ def test_leaking_password_in_clone_logs(self, rw_dir):
263265
to_path=rw_dir,
264266
)
265267

268+
deftest_unsafe_options(self):
269+
self.assertFalse(Repo.unsafe_options("github.com/deploy/deploy"))
270+
271+
deftest_unsafe_options_ext_url(self):
272+
self.assertTrue(Repo.unsafe_options("ext::ssh"))
273+
274+
deftest_unsafe_options_multi_options_upload_pack(self):
275+
self.assertTrue(Repo.unsafe_options("", ["--upload-pack='touch foo'"]))
276+
277+
deftest_unsafe_options_multi_options_config_user(self):
278+
self.assertFalse(Repo.unsafe_options("", ["--config user"]))
279+
280+
deftest_unsafe_options_multi_options_config_protocol(self):
281+
self.assertTrue(Repo.unsafe_options("", ["--config protocol.foo"]))
282+
283+
deftest_clone_from_forbids_helper_urls_by_default(self):
284+
withself.assertRaises(UnsafeOptionsUsedError):
285+
Repo.clone_from("ext::sh -c touch% /tmp/foo","tmp")
286+
287+
@with_rw_repo("HEAD")
288+
deftest_clone_from_allow_unsafe(self,repo):
289+
bad_filename=pathlib.Path(f'{tempfile.gettempdir()}/{uuid.uuid4()}')
290+
bad_url=f'ext::sh -c touch%{bad_filename}'
291+
try:
292+
repo.clone_from(
293+
bad_url,'tmp',
294+
multi_options=["-c protocol.ext.allow=always"],
295+
unsafe_protocols=True
296+
)
297+
exceptGitCommandError:
298+
pass
299+
self.assertTrue(bad_filename.is_file())
300+
bad_filename.unlink()
301+
266302
@with_rw_repo("HEAD")
267303
deftest_max_chunk_size(self,repo):
268304
classTestOutputStream(TestBase):

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp