summaryrefslogtreecommitdiffstats
path: root/project.py
diff options
context:
space:
mode:
authorMike Frysinger <vapier@google.com>2019-08-02 15:57:57 -0400
committerMike Frysinger <vapier@google.com>2020-02-04 20:34:23 +0000
commite6a202f790daaf204513b8c53b824fcc246f9972 (patch)
tree6907f26e5a17a7b39f62e401b895088f1c178540 /project.py
parent04122b7261319dae3abcaf0eb63af7ed937dc463 (diff)
downloadgit-repo-e6a202f790daaf204513b8c53b824fcc246f9972.tar.gz
project: add basic path checks for <copyfile> & <linkfile>
Reject paths in <copyfile> & <linkfile> that try to use symlinks or non-file or non-dirs. We don't fully validate <linkfile> when src is a glob as it's a bit complicated -- any component in the src could be the glob. We make sure the destination is a directory, and that any paths in that dir are created as symlinks. So while this can be used to read any path, it can't be abused to write to any paths. Bug: https://crbug.com/gerrit/11218 Change-Id: I68b6d789b5ca4e43f569e75e8b293b3e13d3224b Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/233074 Tested-by: Mike Frysinger <vapier@google.com> Reviewed-by: Mike Frysinger <vapier@google.com> Reviewed-by: Michael Mortensen <mmortensen@google.com>
Diffstat (limited to 'project.py')
-rwxr-xr-xproject.py146
1 files changed, 108 insertions, 38 deletions
diff --git a/project.py b/project.py
index d12d4666..24fbf4f0 100755
--- a/project.py
+++ b/project.py
@@ -36,7 +36,7 @@ from git_command import GitCommand, git_require
36from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \ 36from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
37 ID_RE 37 ID_RE
38from error import GitError, HookError, UploadError, DownloadError 38from error import GitError, HookError, UploadError, DownloadError
39from error import ManifestInvalidRevisionError 39from error import ManifestInvalidRevisionError, ManifestInvalidPathError
40from error import NoManifestException 40from error import NoManifestException
41import platform_utils 41import platform_utils
42import progress 42import progress
@@ -261,17 +261,70 @@ class _Annotation(object):
261 self.keep = keep 261 self.keep = keep
262 262
263 263
264def _SafeExpandPath(base, subpath, skipfinal=False):
265 """Make sure |subpath| is completely safe under |base|.
266
267 We make sure no intermediate symlinks are traversed, and that the final path
268 is not a special file (e.g. not a socket or fifo).
269
270 NB: We rely on a number of paths already being filtered out while parsing the
271 manifest. See the validation logic in manifest_xml.py for more details.
272 """
273 components = subpath.split(os.path.sep)
274 if skipfinal:
275 # Whether the caller handles the final component itself.
276 finalpart = components.pop()
277
278 path = base
279 for part in components:
280 if part in {'.', '..'}:
281 raise ManifestInvalidPathError(
282 '%s: "%s" not allowed in paths' % (subpath, part))
283
284 path = os.path.join(path, part)
285 if platform_utils.islink(path):
286 raise ManifestInvalidPathError(
287 '%s: traversing symlinks not allow' % (path,))
288
289 if os.path.exists(path):
290 if not os.path.isfile(path) and not platform_utils.isdir(path):
291 raise ManifestInvalidPathError(
292 '%s: only regular files & directories allowed' % (path,))
293
294 if skipfinal:
295 path = os.path.join(path, finalpart)
296
297 return path
298
299
264class _CopyFile(object): 300class _CopyFile(object):
301 """Container for <copyfile> manifest element."""
302
303 def __init__(self, git_worktree, src, topdir, dest):
304 """Register a <copyfile> request.
265 305
266 def __init__(self, src, dest, abssrc, absdest): 306 Args:
307 git_worktree: Absolute path to the git project checkout.
308 src: Relative path under |git_worktree| of file to read.
309 topdir: Absolute path to the top of the repo client checkout.
310 dest: Relative path under |topdir| of file to write.
311 """
312 self.git_worktree = git_worktree
313 self.topdir = topdir
267 self.src = src 314 self.src = src
268 self.dest = dest 315 self.dest = dest
269 self.abs_src = abssrc
270 self.abs_dest = absdest
271 316
272 def _Copy(self): 317 def _Copy(self):
273 src = self.abs_src 318 src = _SafeExpandPath(self.git_worktree, self.src)
274 dest = self.abs_dest 319 dest = _SafeExpandPath(self.topdir, self.dest)
320
321 if platform_utils.isdir(src):
322 raise ManifestInvalidPathError(
323 '%s: copying from directory not supported' % (self.src,))
324 if platform_utils.isdir(dest):
325 raise ManifestInvalidPathError(
326 '%s: copying to directory not allowed' % (self.dest,))
327
275 # copy file if it does not exist or is out of date 328 # copy file if it does not exist or is out of date
276 if not os.path.exists(dest) or not filecmp.cmp(src, dest): 329 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
277 try: 330 try:
@@ -292,13 +345,21 @@ class _CopyFile(object):
292 345
293 346
294class _LinkFile(object): 347class _LinkFile(object):
348 """Container for <linkfile> manifest element."""
295 349
296 def __init__(self, git_worktree, src, dest, relsrc, absdest): 350 def __init__(self, git_worktree, src, topdir, dest):
351 """Register a <linkfile> request.
352
353 Args:
354 git_worktree: Absolute path to the git project checkout.
355 src: Target of symlink relative to path under |git_worktree|.
356 topdir: Absolute path to the top of the repo client checkout.
357 dest: Relative path under |topdir| of symlink to create.
358 """
297 self.git_worktree = git_worktree 359 self.git_worktree = git_worktree
360 self.topdir = topdir
298 self.src = src 361 self.src = src
299 self.dest = dest 362 self.dest = dest
300 self.src_rel_to_dest = relsrc
301 self.abs_dest = absdest
302 363
303 def __linkIt(self, relSrc, absDest): 364 def __linkIt(self, relSrc, absDest):
304 # link file if it does not exist or is out of date 365 # link file if it does not exist or is out of date
@@ -316,35 +377,37 @@ class _LinkFile(object):
316 _error('Cannot link file %s to %s', relSrc, absDest) 377 _error('Cannot link file %s to %s', relSrc, absDest)
317 378
318 def _Link(self): 379 def _Link(self):
319 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards 380 """Link the self.src & self.dest paths.
320 on the src linking all of the files in the source in to the destination 381
321 directory. 382 Handles wild cards on the src linking all of the files in the source in to
383 the destination directory.
322 """ 384 """
323 # We use the absSrc to handle the situation where the current directory 385 src = _SafeExpandPath(self.git_worktree, self.src)
324 # is not the root of the repo 386
325 absSrc = os.path.join(self.git_worktree, self.src) 387 if os.path.exists(src):
326 if os.path.exists(absSrc): 388 # Entity exists so just a simple one to one link operation.
327 # Entity exists so just a simple one to one link operation 389 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
328 self.__linkIt(self.src_rel_to_dest, self.abs_dest) 390 # dest & src are absolute paths at this point. Make sure the target of
391 # the symlink is relative in the context of the repo client checkout.
392 relpath = os.path.relpath(src, os.path.dirname(dest))
393 self.__linkIt(relpath, dest)
329 else: 394 else:
395 dest = _SafeExpandPath(self.topdir, self.dest)
330 # Entity doesn't exist assume there is a wild card 396 # Entity doesn't exist assume there is a wild card
331 absDestDir = self.abs_dest 397 if os.path.exists(dest) and not platform_utils.isdir(dest):
332 if os.path.exists(absDestDir) and not platform_utils.isdir(absDestDir): 398 _error('Link error: src with wildcard, %s must be a directory', dest)
333 _error('Link error: src with wildcard, %s must be a directory',
334 absDestDir)
335 else: 399 else:
336 absSrcFiles = glob.glob(absSrc) 400 for absSrcFile in glob.glob(src):
337 for absSrcFile in absSrcFiles:
338 # Create a releative path from source dir to destination dir 401 # Create a releative path from source dir to destination dir
339 absSrcDir = os.path.dirname(absSrcFile) 402 absSrcDir = os.path.dirname(absSrcFile)
340 relSrcDir = os.path.relpath(absSrcDir, absDestDir) 403 relSrcDir = os.path.relpath(absSrcDir, dest)
341 404
342 # Get the source file name 405 # Get the source file name
343 srcFile = os.path.basename(absSrcFile) 406 srcFile = os.path.basename(absSrcFile)
344 407
345 # Now form the final full paths to srcFile. They will be 408 # Now form the final full paths to srcFile. They will be
346 # absolute for the desintaiton and relative for the srouce. 409 # absolute for the desintaiton and relative for the srouce.
347 absDest = os.path.join(absDestDir, srcFile) 410 absDest = os.path.join(dest, srcFile)
348 relSrc = os.path.join(relSrcDir, srcFile) 411 relSrc = os.path.join(relSrcDir, srcFile)
349 self.__linkIt(relSrc, absDest) 412 self.__linkIt(relSrc, absDest)
350 413
@@ -1712,18 +1775,25 @@ class Project(object):
1712 if submodules: 1775 if submodules:
1713 syncbuf.later1(self, _dosubmodules) 1776 syncbuf.later1(self, _dosubmodules)
1714 1777
1715 def AddCopyFile(self, src, dest, absdest): 1778 def AddCopyFile(self, src, dest, topdir):
1716 # dest should already be an absolute path, but src is project relative 1779 """Mark |src| for copying to |dest| (relative to |topdir|).
1717 # make src an absolute path 1780
1718 abssrc = os.path.join(self.worktree, src) 1781 No filesystem changes occur here. Actual copying happens later on.
1719 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest)) 1782
1720 1783 Paths should have basic validation run on them before being queued.
1721 def AddLinkFile(self, src, dest, absdest): 1784 Further checking will be handled when the actual copy happens.
1722 # dest should already be an absolute path, but src is project relative 1785 """
1723 # make src relative path to dest 1786 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1724 absdestdir = os.path.dirname(absdest) 1787
1725 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir) 1788 def AddLinkFile(self, src, dest, topdir):
1726 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest)) 1789 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1790
1791 No filesystem changes occur here. Actual linking happens later on.
1792
1793 Paths should have basic validation run on them before being queued.
1794 Further checking will be handled when the actual link happens.
1795 """
1796 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
1727 1797
1728 def AddAnnotation(self, name, value, keep): 1798 def AddAnnotation(self, name, value, keep):
1729 self.annotations.append(_Annotation(name, value, keep)) 1799 self.annotations.append(_Annotation(name, value, keep))