Merge pull request #2134 from gitpython-developers/validate-ref-creation · gitpython-developers/GitPython@dbfa264 · GitHub
Skip to content

Commit dbfa264

Browse files
authored
Merge pull request #2134 from gitpython-developers/validate-ref-creation
prevent out-of-repo access when manipulating references.
2 parents 4199cb8 + 4af8463 commit dbfa264

5 files changed

Lines changed: 164 additions & 11 deletions

File tree

git/refs/log.py

Lines changed: 4 additions & 2 deletions

git/refs/remote.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,20 @@ def delete(cls, repo: "Repo", *refs: "RemoteReference", **kwargs: Any) -> None:
5858
`kwargs` are given for comparability with the base class method as we
5959
should not narrow the signature.
6060
"""
61+
for ref in refs:
62+
cls._check_ref_name_valid(ref.path)
63+
6164
repo.git.branch("-d", "-r", *refs)
6265
# The official deletion method will ignore remote symbolic refs - these are
6366
# generally ignored in the refs/ folder. We don't though and delete remainders
6467
# manually.
6568
for ref in refs:
6669
try:
67-
os.remove(os.path.join(repo.common_dir, ref.path))
70+
os.remove(cls._get_validated_path(repo.common_dir, ref.path))
6871
except OSError:
6972
pass
7073
try:
71-
os.remove(os.path.join(repo.git_dir, ref.path))
74+
os.remove(cls._get_validated_path(repo.git_dir, ref.path))
7275
except OSError:
7376
pass
7477
# 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.

git/util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ def join_path(a: PathLike, *p: PathLike) -> PathLike:
289289

290290
if sys.platform == "win32":
291291

292-
def to_native_path_windows(path: PathLike) -> PathLike:
292+
def to_native_path_windows(path: PathLike) -> str:
293293
path = os.fspath(path)
294294
return path.replace("/", "\\")
295295

test/test_refs.py

Lines changed: 123 additions & 0 deletions

0 commit comments

Comments
 (0)