Skip to content

Commit 8a14adc

Browse files
authored
Merge pull request #2225 from gitpython-developers/submodule-path-hardening
Reject unsafe submodule checkout paths
2 parents 1d47514 + 1ed0ebc commit 8a14adc

3 files changed

Lines changed: 65 additions & 0 deletions

File tree

doc/source/changes.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22
Changelog
33
=========
44

5+
3.1.62
6+
======
7+
8+
Security fixes for
9+
10+
* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-59cr-6r3x-644w
11+
12+
If you can, also try and provide feedback on the upcoming v4 branch
13+
https://github.com/gitpython-developers/GitPython/pull/2177 - patches welcome.
14+
15+
See the following for all changes.
16+
https://github.com/gitpython-developers/GitPython/releases/tag/3.1.62
17+
518
3.1.61
619
======
720

git/objects/submodule/base.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,18 @@ def _to_relative_path(cls, parent_repo: "Repo", path: PathLike) -> PathLike:
414414

415415
return path
416416

417+
@property
418+
def abspath(self) -> PathLike:
419+
root = self.repo.working_tree_dir
420+
if root is None:
421+
return super().abspath
422+
path = root
423+
for component in os.fspath(self._to_relative_path(self.repo, self.path)).split("/"):
424+
path = join_path_native(path, component)
425+
if osp.islink(path):
426+
raise ValueError("Submodule checkout path %r contains a symbolic link" % self.path)
427+
return path
428+
417429
@classmethod
418430
def _write_git_file_and_module_config(cls, working_tree_dir: PathLike, module_abspath: PathLike) -> None:
419431
"""Write a ``.git`` file containing a (preferably) relative path to the actual

test/test_submodule.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1369,6 +1369,46 @@ class Repo:
13691369
osp.join(Repo.working_tree_dir + "-other", "module"),
13701370
)
13711371

1372+
@with_rw_directory
1373+
def test_update_rejects_checkout_path_outside_parent(self, rwdir):
1374+
parent = git.Repo.init(osp.join(rwdir, "parent"))
1375+
submodule = Submodule(
1376+
parent,
1377+
Submodule.NULL_BIN_SHA,
1378+
name="module",
1379+
path=osp.join("..", "outside"),
1380+
url="unused",
1381+
)
1382+
1383+
with mock.patch.object(Submodule, "_clone_repo", side_effect=AssertionError("clone attempted")):
1384+
with pytest.raises(ValueError, match="is not in repository"):
1385+
submodule.update(init=True)
1386+
1387+
@with_rw_directory
1388+
def test_update_rejects_checkout_path_through_symlink(self, rwdir):
1389+
parent = git.Repo.init(osp.join(rwdir, "parent"))
1390+
os.mkdir(osp.join(parent.working_tree_dir, "target"))
1391+
os.symlink("target", osp.join(parent.working_tree_dir, "link"))
1392+
submodule = Submodule(
1393+
parent,
1394+
Submodule.NULL_BIN_SHA,
1395+
name="module",
1396+
path=osp.join("link", "module"),
1397+
url="unused",
1398+
)
1399+
1400+
with mock.patch.object(Submodule, "_clone_repo", side_effect=AssertionError("clone attempted")):
1401+
with pytest.raises(ValueError, match="contains a symbolic link"):
1402+
submodule.update(init=True)
1403+
1404+
@with_rw_directory
1405+
def test_update_rejects_checkout_path_at_parent_root(self, rwdir):
1406+
parent = git.Repo.init(osp.join(rwdir, "parent"))
1407+
submodule = Submodule(parent, Submodule.NULL_BIN_SHA, name="module", path=".", url="unused")
1408+
1409+
with pytest.raises(ValueError, match="must not be the repository root"):
1410+
submodule.update(init=True)
1411+
13721412
@skipUnless(sys.platform == "win32", "Specifically for Windows.")
13731413
@with_rw_directory
13741414
def test_to_relative_path_windows_path_kinds(self, rwdir):

0 commit comments

Comments
 (0)