diff --git a/reposync-prevent-path-traversal.patch b/reposync-prevent-path-traversal.patch new file mode 100644 index 0000000..026778d --- /dev/null +++ b/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-08-31 15:14:08.100322216 +0200 ++++ yum-utils-1.1.31/docs/reposync.1 2018-08-31 15:14:14.680403500 +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-08-31 15:14:08.112322364 +0200 ++++ yum-utils-1.1.31/reposync.py 2018-08-31 15:14:14.680403500 +0200 +@@ -75,6 +75,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 +@@ -117,6 +123,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) + +@@ -217,13 +227,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: +@@ -270,8 +303,7 @@ def main(): + remote_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 +@@ -283,10 +315,9 @@ def main(): + download_list.sort(key=lambda pkg: pkg.name) + 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 +@@ -295,8 +326,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/yum-utils.spec b/yum-utils.spec index 6a0feee..b4055d8 100644 --- a/yum-utils.spec +++ b/yum-utils.spec @@ -10,12 +10,13 @@ Summary: Utilities based around the yum package manager Name: yum-utils Version: 1.1.31 -Release: 514%{?dist} +Release: 515%{?dist} License: GPLv2+ Group: Development/Tools Source: http://yum.baseurl.org/download/yum-utils/%{name}-%{version}.tar.gz Patch1: yum-utils-HEAD.patch Patch2: yum-utils-deprecated.patch +Patch3: reposync-prevent-path-traversal.patch URL: http://yum.baseurl.org/download/yum-utils/ BuildArch: noarch Requires: yum >= 3.4.3-148 @@ -374,6 +375,7 @@ This plugin touches rpmdb files to work around overlayfs issues. %setup -q %patch1 -p1 %patch2 -p1 +%patch3 -p1 %install rm -rf $RPM_BUILD_ROOT @@ -673,6 +675,10 @@ touch $RPM_BUILD_ROOT%{_sysconfdir}/yum.repos.d/_local.repo %{pluginhome}/ovl.* %changelog +* Fri Aug 31 2018 Michal Domonkos - 1.1.31-515 +- reposync: prevent path traversal (CVE-2018-10897) +- Resolves: bug#1600454 + * Fri Feb 09 2018 Fedora Release Engineering - 1.1.31-514 - Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild