diff --git a/SOURCES/BZ-1600617-reposync-prevent-path-traversal.patch b/SOURCES/BZ-1600617-reposync-prevent-path-traversal.patch new file mode 100644 index 0000000..a478370 --- /dev/null +++ b/SOURCES/BZ-1600617-reposync-prevent-path-traversal.patch @@ -0,0 +1,119 @@ +diff -up yum-utils-1.1.31/docs/reposync.1.orig yum-utils-1.1.31/docs/reposync.1 +--- yum-utils-1.1.31/docs/reposync.1.orig 2018-07-20 11:01:16.789747160 +0200 ++++ yum-utils-1.1.31/docs/reposync.1 2018-07-20 11:01:21.765700655 +0200 +@@ -47,6 +47,16 @@ Download all the non-default metadata. + Download only newest packages per-repo. + .IP "\fB\-q, \-\-quiet\fP" + Output as little information as possible. ++.IP "\fB\-\-allow-path-traversal\fP" ++Allow packages stored outside their repo directory to be synced. ++These are packages that are referenced in metadata by using absolute paths or ++up-level ".." symbols, and are normally skipped by \fBreposync\fR for security ++reasons. ++ ++\fBCAUTION:\fR Using this option has potential security implications since, by ++providing malicious repodata, an attacker could make \fBreposync\fR write to ++arbitrary locations on the file system that are accessible by the user running ++it. + .SH "EXAMPLES" + .IP "Sync all packages from the 'updates' repo to the current directory:" + \fB reposync \-\-repoid=updates\fP +diff -up yum-utils-1.1.31/reposync.py.orig yum-utils-1.1.31/reposync.py +--- yum-utils-1.1.31/reposync.py.orig 2018-07-20 11:01:16.752747506 +0200 ++++ yum-utils-1.1.31/reposync.py 2018-07-20 11:01:54.717392693 +0200 +@@ -84,6 +84,12 @@ def localpkgs(directory): + cache[name] = { 'path': fn, 'size': st.st_size, 'device': st.st_dev } + return cache + ++def is_subpath(path, root): ++ root = os.path.realpath(root) ++ path = os.path.realpath(os.path.join(root, path)) ++ # join() is used below to ensure root ends with a slash ++ return path.startswith(os.path.join(root, '')) ++ + def parseArgs(): + usage = """ + Reposync is used to synchronize a remote yum repository to a local +@@ -126,6 +132,10 @@ def parseArgs(): + parser.add_option("","--download-metadata", dest="downloadmd", + default=False, action="store_true", + help="download all the non-default metadata") ++ parser.add_option("", "--allow-path-traversal", default=False, ++ action="store_true", ++ help="Allow packages stored outside their repo directory to be synced " ++ "(UNSAFE, USE WITH CAUTION!)") + (opts, args) = parser.parse_args() + return (opts, args) + +@@ -226,13 +236,36 @@ def main(): + else: + local_repo_path = opts.destdir + '/' + repo.id + ++ # Ensure we don't traverse out of local_repo_path by dropping any ++ # packages whose remote_path is absolute or contains up-level ++ # references (unless explicitly allowed). ++ # See RHBZ#1600221 for details. ++ if not opts.allow_path_traversal: ++ newlist = [] ++ skipped = False ++ for pkg in download_list: ++ if is_subpath(pkg.remote_path, local_repo_path): ++ newlist.append(pkg) ++ continue ++ my.logger.warning( ++ 'WARNING: skipping package %s: remote path "%s" not ' ++ 'within repodir, unsafe to mirror locally' ++ % (pkg, pkg.remote_path) ++ ) ++ skipped = True ++ if skipped: ++ my.logger.info( ++ 'You can enable unsafe remote paths by using ' ++ '--allow-path-traversal (see reposync(1) for details)' ++ ) ++ download_list = newlist ++ + if opts.delete and os.path.exists(local_repo_path): + current_pkgs = localpkgs(local_repo_path) + + download_set = {} + for pkg in download_list: +- remote = pkg.returnSimple('relativepath') +- rpmname = os.path.basename(remote) ++ rpmname = os.path.basename(pkg.remote_path) + download_set[rpmname] = 1 + + for pkg in current_pkgs: +@@ -280,8 +313,7 @@ def main(): + local_size = 0 + if not opts.urls: + for pkg in download_list: +- remote = pkg.returnSimple('relativepath') +- local = local_repo_path + '/' + remote ++ local = os.path.join(local_repo_path, pkg.remote_path) + sz = int(pkg.returnSimple('packagesize')) + if os.path.exists(local) and os.path.getsize(local) == sz: + continue +@@ -293,10 +325,9 @@ def main(): + download_list.sort(sortPkgObj) + if opts.urls: + for pkg in download_list: +- remote = pkg.returnSimple('relativepath') +- local = os.path.join(local_repo_path, remote) ++ local = os.path.join(local_repo_path, pkg.remote_path) + if not (os.path.exists(local) and my.verifyPkg(local, pkg, False)): +- print urljoin(pkg.repo.urls[0], pkg.relativepath) ++ print urljoin(pkg.repo.urls[0], pkg.remote_path) + continue + + # create dest dir +@@ -305,8 +336,7 @@ def main(): + + # set localpaths + for pkg in download_list: +- rpmfn = pkg.remote_path +- pkg.localpath = os.path.join(local_repo_path, rpmfn) ++ pkg.localpath = os.path.join(local_repo_path, pkg.remote_path) + pkg.repo.copy_local = True + pkg.repo.cache = 0 + localdir = os.path.dirname(pkg.localpath) diff --git a/SPECS/yum-utils.spec b/SPECS/yum-utils.spec index 0d479fc..24eb629 100644 --- a/SPECS/yum-utils.spec +++ b/SPECS/yum-utils.spec @@ -12,7 +12,7 @@ Summary: Utilities based around the yum package manager Name: yum-utils Version: 1.1.31 -Release: 45%{?dist} +Release: 46%{?dist} License: GPLv2+ Group: Development/Tools Source: http://yum.baseurl.org/download/yum-utils/%{name}-%{version}.tar.gz @@ -72,6 +72,7 @@ Patch165: BZ-1437636-yum-builddep-add-define-opt.patch Patch166: BZ-1349433-verifytree-handle-no-core-group.patch Patch167: BZ-1333353-verifytree-fix-handling-no-comps.patch Patch168: BZ-1127783-transaction-actions-fix-file-globs.patch +Patch169: BZ-1600617-reposync-prevent-path-traversal.patch URL: http://yum.baseurl.org/download/yum-utils/ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) @@ -511,6 +512,7 @@ This plugin touches rpmdb files to work around overlayfs issues. %patch166 -p1 %patch167 -p1 %patch168 -p1 +%patch169 -p1 %install rm -rf $RPM_BUILD_ROOT @@ -842,6 +844,10 @@ fi %{_mandir}/man1/yum-ovl.1.* %changelog +* Fri Jul 20 2018 Michal Domonkos - 1.1.31-46 +- reposync: prevent path traversal. +- Resolves: bug#1600617 + * Tue Nov 21 2017 Valentina Mukhamedzhanova - 1.1.31-45 - Fix file globbing in transaction-actions. - Related: bug#1470647