prevent out-of-repo access when manipulating references. · gitpython-developers/GitPython@25ba54d · GitHub
Skip to content

Commit 25ba54d

Browse files
codexByron
andcommitted
prevent out-of-repo access when manipulating references.
This previously made it possible to create, modify and delete files outside outside of the repository, which is a problem if inputs aren't trusted. Co-authored-by: Sebastian Thiel <sebastian.thiel@icloud.com>
1 parent 4199cb8 commit 25ba54d

4 files changed

Lines changed: 126 additions & 9 deletions

File tree

git/refs/log.py

Lines changed: 1 addition & 1 deletion

git/refs/remote.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,13 @@ def delete(cls, repo: "Repo", *refs: "RemoteReference", **kwargs: Any) -> None:
6363
# generally ignored in the refs/ folder. We don't though and delete remainders
6464
# manually.
6565
for ref in refs:
66+
cls._check_ref_name_valid(ref.path)
6667
try:
67-
os.remove(os.path.join(repo.common_dir, ref.path))
68+
os.remove(cls._get_validated_path(repo.common_dir, ref.path))
6869
except OSError:
6970
pass
7071
try:
71-
os.remove(os.path.join(repo.git_dir, ref.path))
72+
os.remove(cls._get_validated_path(repo.git_dir, ref.path))
7273
except OSError:
7374
pass
7475
# END for each ref

git/refs/symbolic.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,32 @@ def name(self) -> str:
110110
def abspath(self) -> PathLike:
111111
return join_path_native(_git_dir(self.repo, self.path), self.path)
112112

113+
@staticmethod
114+
def _get_validated_path(base: PathLike, path: PathLike) -> str:
115+
path = os.fspath(path)
116+
base_path = os.path.realpath(os.fspath(base))
117+
abs_path = os.path.realpath(os.path.join(base_path, path))
118+
try:
119+
common_path = os.path.commonpath([base_path, abs_path])
120+
except ValueError as e:
121+
raise ValueError("Reference path %r escapes the repository" % path) from e
122+
if os.path.normcase(common_path) != os.path.normcase(base_path):
123+
raise ValueError("Reference path %r escapes the repository" % path)
124+
return abs_path
125+
126+
@classmethod
127+
def _get_validated_ref_path(cls, repo: "Repo", path: PathLike) -> str:
128+
"""Return the absolute filesystem path for a ref after validating it."""
129+
cls._check_ref_name_valid(path)
130+
ref_path = os.fspath(path)
131+
return cls._get_validated_path(_git_dir(repo, ref_path), ref_path)
132+
133+
@classmethod
134+
def _get_validated_reflog_path(cls, repo: "Repo", path: PathLike) -> str:
135+
"""Return the absolute filesystem path for a reflog after validating it."""
136+
cls._check_ref_name_valid(path)
137+
return cls._get_validated_path(os.path.join(repo.git_dir, "logs"), path)
138+
113139
@classmethod
114140
def _get_packed_refs_path(cls, repo: "Repo") -> str:
115141
return os.path.join(repo.common_dir, "packed-refs")
@@ -485,7 +511,7 @@ def set_reference(
485511
# END handle non-existing
486512
# END retrieve old hexsha
487513

488-
fpath = self.abspath
514+
fpath = self._get_validated_ref_path(self.repo, self.path)
489515
assure_directory_exists(fpath, is_file=True)
490516

491517
lfd = LockedFD(fpath)
@@ -632,7 +658,7 @@ def delete(cls, repo: "Repo", path: PathLike) -> None:
632658
Alternatively the symbolic reference to be deleted.
633659
"""
634660
full_ref_path = cls.to_full_path(path)
635-
abs_path = os.path.join(repo.common_dir, full_ref_path)
661+
abs_path = cls._get_validated_ref_path(repo, full_ref_path)
636662
if os.path.exists(abs_path):
637663
os.remove(abs_path)
638664
else:
@@ -695,9 +721,8 @@ def _create(
695721
symbolic reference. Otherwise it will be resolved to the corresponding object
696722
and a detached symbolic reference will be created instead.
697723
"""
698-
git_dir = _git_dir(repo, path)
699724
full_ref_path = cls.to_full_path(path)
700-
abs_ref_path = os.path.join(git_dir, full_ref_path)
725+
abs_ref_path = cls._get_validated_ref_path(repo, full_ref_path)
701726

702727
# Figure out target data.
703728
target = reference
@@ -789,8 +814,8 @@ def rename(self, new_path: PathLike, force: bool = False) -> "SymbolicReference"
789814
if self.path == new_path:
790815
return self
791816

792-
new_abs_path = os.path.join(_git_dir(self.repo, new_path), new_path)
793-
cur_abs_path = os.path.join(_git_dir(self.repo, self.path), self.path)
817+
new_abs_path = self._get_validated_ref_path(self.repo, new_path)
818+
cur_abs_path = self._get_validated_ref_path(self.repo, self.path)
794819
if os.path.isfile(new_abs_path):
795820
if not force:
796821
# If they point to the same file, it's not an error.

test/test_refs.py

Lines changed: 91 additions & 0 deletions

0 commit comments

Comments
 (0)