Merge "Fix safe path check for directories containing symlinks"

This commit is contained in:
Zuul 2018-03-14 20:24:34 +00:00 committed by Gerrit Code Review
commit fe0274cfca
15 changed files with 78 additions and 6 deletions

1
tests/fixtures/bwrap-mounts/dir/file vendored Normal file
View File

@ -0,0 +1 @@
test

View File

@ -0,0 +1,4 @@
- hosts: all
roles:
- role: assemble-test
src_file: dir-symlink

View File

@ -0,0 +1,4 @@
- hosts: all
roles:
- role: copy-test
src_file: dir-symlink

View File

@ -0,0 +1,4 @@
- hosts: all
roles:
- role: includevarsdir-test
src_file: dir-double-symlink

View File

@ -0,0 +1,4 @@
- hosts: all
roles:
- role: includevarsdir-test
src_file: dir-symlink

View File

@ -83,12 +83,16 @@ class TestActionModules(AnsibleZuulTestCase):
self._run_job('assemble-bad', 'FAILURE', ERROR_ACCESS_OUTSIDE)
self._run_job('assemble-bad-symlink', 'FAILURE', ERROR_ACCESS_OUTSIDE)
self._run_job('assemble-bad-dir-with-symlink', 'FAILURE',
ERROR_ACCESS_OUTSIDE)
def test_copy_module(self):
self._run_job('copy-good', 'SUCCESS')
self._run_job('copy-bad', 'FAILURE', ERROR_ACCESS_OUTSIDE)
self._run_job('copy-bad-symlink', 'FAILURE', ERROR_ACCESS_OUTSIDE)
self._run_job('copy-bad-dir-with-symlink', 'FAILURE',
ERROR_ACCESS_OUTSIDE)
def test_includevars_module(self):
self._run_job('includevars-good', 'SUCCESS')
@ -100,6 +104,10 @@ class TestActionModules(AnsibleZuulTestCase):
self._run_job('includevars-bad-dir', 'FAILURE', ERROR_ACCESS_OUTSIDE)
self._run_job('includevars-bad-dir-symlink', 'FAILURE',
ERROR_ACCESS_OUTSIDE)
self._run_job('includevars-bad-dir-with-symlink', 'FAILURE',
ERROR_ACCESS_OUTSIDE)
self._run_job('includevars-bad-dir-with-double-symlink', 'FAILURE',
ERROR_ACCESS_OUTSIDE)
def test_patch_module(self):
self._run_job('patch-good', 'SUCCESS')

View File

@ -38,15 +38,50 @@ def _full_path(path):
def _is_safe_path(path, allow_trusted=False):
full_path = _full_path(path)
home_path = os.path.abspath(os.path.expanduser('~'))
if not full_path.startswith(home_path):
if allow_trusted:
trusted_path = os.path.abspath(
os.path.join(home_path, '../trusted'))
if full_path.startswith(trusted_path):
allowed_paths = [home_path]
if allow_trusted:
allowed_paths.append(
os.path.abspath(os.path.join(home_path, '../trusted')))
def _is_safe(path_to_check):
for allowed_path in allowed_paths:
if path_to_check.startswith(allowed_path):
return True
return False
# We need to really check the whole subtree starting from path. So first
# start with the root and do an os.walk if path resolves to a directory.
full_path = _full_path(path)
if not _is_safe(full_path):
return False
# Walk the whole tree and check dirs and files. In order to mitigate
# chained symlink attacks we also need to follow symlinks.
visited = set()
for root, dirs, files in os.walk(full_path, followlinks=True):
# We recurse with follow links so check root first, then the files.
# The dirs will be checked during recursion.
full_root = _full_path(root)
if not _is_safe(full_root):
return False
# NOTE: os.walk can lead to infinite recursion when following links
# so filter out the dirs for further processing if we already checked
# this one.
if full_root in visited:
del dirs[:]
# we already checked the files here so we can just continue to the
# next iteration
continue
visited.add(full_root)
for entry in files:
full_path = _full_path(os.path.join(root, entry))
if not _is_safe(full_path):
return False
return True