Skip to content

Commit

Permalink
reposync: prevent path traversal. BZ 1552328
Browse files Browse the repository at this point in the history
  • Loading branch information
dmnks committed Jul 20, 2018
1 parent 7554c01 commit 6a8de06
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 0 deletions.
10 changes: 10 additions & 0 deletions docs/reposync.1
Expand Up @@ -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
Expand Down
34 changes: 34 additions & 0 deletions reposync.py
Expand Up @@ -74,6 +74,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
Expand Down Expand Up @@ -116,6 +122,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)

Expand Down Expand Up @@ -216,6 +226,30 @@ 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)

Expand Down

0 comments on commit 6a8de06

Please sign in to comment.