Blame SOURCES/BZ-1600617-reposync-prevent-path-traversal.patch

c3be86
diff -up yum-utils-1.1.31/docs/reposync.1.orig yum-utils-1.1.31/docs/reposync.1
c3be86
--- yum-utils-1.1.31/docs/reposync.1.orig	2018-07-20 11:01:16.789747160 +0200
c3be86
+++ yum-utils-1.1.31/docs/reposync.1	2018-07-20 11:01:21.765700655 +0200
c3be86
@@ -47,6 +47,16 @@ Download all the non-default metadata.
c3be86
 Download only newest packages per-repo.
c3be86
 .IP "\fB\-q, \-\-quiet\fP"
c3be86
 Output as little information as possible.
c3be86
+.IP "\fB\-\-allow-path-traversal\fP"
c3be86
+Allow packages stored outside their repo directory to be synced.
c3be86
+These are packages that are referenced in metadata by using absolute paths or
c3be86
+up-level ".." symbols, and are normally skipped by \fBreposync\fR for security
c3be86
+reasons.
c3be86
+
c3be86
+\fBCAUTION:\fR Using this option has potential security implications since, by
c3be86
+providing malicious repodata, an attacker could make \fBreposync\fR write to
c3be86
+arbitrary locations on the file system that are accessible by the user running
c3be86
+it.
c3be86
 .SH "EXAMPLES"
c3be86
 .IP "Sync all packages from the 'updates' repo to the current directory:"
c3be86
 \fB reposync \-\-repoid=updates\fP
c3be86
diff -up yum-utils-1.1.31/reposync.py.orig yum-utils-1.1.31/reposync.py
c3be86
--- yum-utils-1.1.31/reposync.py.orig	2018-07-20 11:01:16.752747506 +0200
c3be86
+++ yum-utils-1.1.31/reposync.py	2018-07-20 11:01:54.717392693 +0200
c3be86
@@ -84,6 +84,12 @@ def localpkgs(directory):
c3be86
             cache[name] = { 'path': fn, 'size': st.st_size, 'device': st.st_dev }
c3be86
     return cache
c3be86
 
c3be86
+def is_subpath(path, root):
c3be86
+    root = os.path.realpath(root)
c3be86
+    path = os.path.realpath(os.path.join(root, path))
c3be86
+    # join() is used below to ensure root ends with a slash
c3be86
+    return path.startswith(os.path.join(root, ''))
c3be86
+
c3be86
 def parseArgs():
c3be86
     usage = """
c3be86
     Reposync is used to synchronize a remote yum repository to a local 
c3be86
@@ -126,6 +132,10 @@ def parseArgs():
c3be86
     parser.add_option("","--download-metadata", dest="downloadmd", 
c3be86
         default=False, action="store_true", 
c3be86
         help="download all the non-default metadata")
c3be86
+    parser.add_option("", "--allow-path-traversal", default=False,
c3be86
+        action="store_true",
c3be86
+        help="Allow packages stored outside their repo directory to be synced "
c3be86
+             "(UNSAFE, USE WITH CAUTION!)")
c3be86
     (opts, args) = parser.parse_args()
c3be86
     return (opts, args)
c3be86
 
c3be86
@@ -226,13 +236,36 @@ def main():
c3be86
         else:
c3be86
             local_repo_path = opts.destdir + '/' + repo.id
c3be86
 
c3be86
+        # Ensure we don't traverse out of local_repo_path by dropping any
c3be86
+        # packages whose remote_path is absolute or contains up-level
c3be86
+        # references (unless explicitly allowed).
c3be86
+        # See RHBZ#1600221 for details.
c3be86
+        if not opts.allow_path_traversal:
c3be86
+            newlist = []
c3be86
+            skipped = False
c3be86
+            for pkg in download_list:
c3be86
+                if is_subpath(pkg.remote_path, local_repo_path):
c3be86
+                    newlist.append(pkg)
c3be86
+                    continue
c3be86
+                my.logger.warning(
c3be86
+                    'WARNING: skipping package %s: remote path "%s" not '
c3be86
+                    'within repodir, unsafe to mirror locally'
c3be86
+                    % (pkg, pkg.remote_path)
c3be86
+                )
c3be86
+                skipped = True
c3be86
+            if skipped:
c3be86
+                my.logger.info(
c3be86
+                    'You can enable unsafe remote paths by using '
c3be86
+                    '--allow-path-traversal (see reposync(1) for details)'
c3be86
+                )
c3be86
+            download_list = newlist
c3be86
+
c3be86
         if opts.delete and os.path.exists(local_repo_path):
c3be86
             current_pkgs = localpkgs(local_repo_path)
c3be86
 
c3be86
             download_set = {}
c3be86
             for pkg in download_list:
c3be86
-                remote = pkg.returnSimple('relativepath')
c3be86
-                rpmname = os.path.basename(remote)
c3be86
+                rpmname = os.path.basename(pkg.remote_path)
c3be86
                 download_set[rpmname] = 1
c3be86
 
c3be86
             for pkg in current_pkgs:
c3be86
@@ -280,8 +313,7 @@ def main():
c3be86
         local_size  = 0
c3be86
         if not opts.urls:
c3be86
             for pkg in download_list:
c3be86
-                remote = pkg.returnSimple('relativepath')
c3be86
-                local = local_repo_path + '/' + remote
c3be86
+                local = os.path.join(local_repo_path, pkg.remote_path)
c3be86
                 sz = int(pkg.returnSimple('packagesize'))
c3be86
                 if os.path.exists(local) and os.path.getsize(local) == sz:
c3be86
                     continue
c3be86
@@ -293,10 +325,9 @@ def main():
c3be86
         download_list.sort(sortPkgObj)
c3be86
         if opts.urls:
c3be86
             for pkg in download_list:
c3be86
-                remote = pkg.returnSimple('relativepath')
c3be86
-                local = os.path.join(local_repo_path, remote)
c3be86
+                local = os.path.join(local_repo_path, pkg.remote_path)
c3be86
                 if not (os.path.exists(local) and my.verifyPkg(local, pkg, False)):
c3be86
-                    print urljoin(pkg.repo.urls[0], pkg.relativepath)
c3be86
+                    print urljoin(pkg.repo.urls[0], pkg.remote_path)
c3be86
             continue
c3be86
 
c3be86
         # create dest dir
c3be86
@@ -305,8 +336,7 @@ def main():
c3be86
 
c3be86
         # set localpaths
c3be86
         for pkg in download_list:
c3be86
-            rpmfn = pkg.remote_path
c3be86
-            pkg.localpath = os.path.join(local_repo_path, rpmfn)
c3be86
+            pkg.localpath = os.path.join(local_repo_path, pkg.remote_path)
c3be86
             pkg.repo.copy_local = True
c3be86
             pkg.repo.cache = 0
c3be86
             localdir = os.path.dirname(pkg.localpath)