| # Copyright 2018 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging |
| import os |
| import subprocess |
| import shutil |
| import tempfile |
| |
| from autotest_lib.client.bin import test, utils |
| from autotest_lib.client.common_lib import error |
| |
| MOUNT_PATH=tempfile.mkdtemp() |
| |
| class security_NosymfollowMountOption(test.test): |
| """ |
| Mount filesystems with the "nosymfollow" option and ensure symlink |
| traversal is blocked. |
| """ |
| version = 1 |
| |
| def __init__(self, *args, **kwargs): |
| # TODO(mortonm): add a function to utils to do this kernel version |
| # check and raise NAError. |
| version = utils.get_kernel_version() |
| if version == "3.8.11": |
| raise error.TestNAError('Test is n/a for kernels older than 3.10') |
| super(security_NosymfollowMountOption, |
| self).__init__(*args, **kwargs) |
| self._failure = False |
| |
| def cleanup(self): |
| """ |
| Clean up test environment. |
| """ |
| super(security_NosymfollowMountOption, self).cleanup() |
| shutil.rmtree(MOUNT_PATH) |
| |
| def _fail(self, msg): |
| """ |
| Log failure message and record failure. |
| |
| @param msg: String to log. |
| |
| """ |
| logging.error(msg) |
| self._failure = True |
| |
| def umount(self): |
| """ |
| Unmount file system at MOUNT_PATH location. |
| """ |
| try: |
| subprocess.check_output(["/bin/umount", MOUNT_PATH]) |
| except subprocess.CalledProcessError, e: |
| self._fail("umount call failed") |
| |
| def mount_and_test_with_string(self, mount_options, restrict_symlinks): |
| """ |
| Mount file system with given options, check it was mounted with |
| correct options, and make sure symlink traversal restriction works as |
| expected. |
| |
| @param mount_options: Mount options string. |
| |
| @param restrict_symlinks: True if mount options should cause symlinks |
| to be restricted, False otherwise. |
| |
| """ |
| try: |
| subprocess.check_output(["/bin/mount", |
| "-n", |
| "-t", |
| "tmpfs", |
| "-o", |
| mount_options, |
| "tmpfs", |
| MOUNT_PATH]) |
| except subprocess.CalledProcessError: |
| self._fail("mount call failed") |
| return |
| |
| try: |
| ps = subprocess.Popen(('mount'), stdout=subprocess.PIPE) |
| output = subprocess.check_output(('grep',MOUNT_PATH), |
| stdin=ps.stdout) |
| ps.wait() |
| |
| for arg in mount_options.split(','): |
| if arg == "nosymfollow": |
| continue |
| else: |
| if output.find(arg) == -1: |
| self._fail("filesystem missing '%s' arg" % arg) |
| return |
| |
| try: |
| open(MOUNT_PATH + "/file", "w+") |
| os.symlink(MOUNT_PATH + "/file", MOUNT_PATH + "/link") |
| except IOError: |
| self._fail("creating/linking files failed") |
| return |
| |
| traversal_restricted = False |
| try: |
| open(MOUNT_PATH + "/link", "r") |
| except IOError: |
| traversal_restricted = True |
| |
| if restrict_symlinks: |
| if not traversal_restricted: |
| self._fail("symlink traversal was not restricted") |
| return |
| else: |
| if traversal_restricted: |
| self._fail("symlink traversal was restricted") |
| finally: |
| self.umount() |
| |
| def run_once(self, test_selinux_interaction): |
| """ |
| Runs the test, mounting filesystems and checking symlink traversal |
| behavior. |
| """ |
| self.mount_and_test_with_string("nosymfollow", True) |
| self.mount_and_test_with_string("nodev,noexec,nosuid,nosymfollow", True) |
| self.mount_and_test_with_string("nodev,noexec,nosuid", False) |
| |
| if test_selinux_interaction: |
| if not os.path.exists('/etc/selinux'): |
| raise error.TestNAError('Test is n/a if selinux is not enabled') |
| self.mount_and_test_with_string("nosymfollow," |
| "context=u:object_r:tmpfs:s0," |
| "fscontext=u:object_r:tmpfs:s0", |
| True) |
| self.mount_and_test_with_string("context=u:object_r:tmpfs:s0," |
| "nosymfollow," |
| "fscontext=u:object_r:tmpfs:s0", |
| True) |
| self.mount_and_test_with_string("context=u:object_r:tmpfs:s0," |
| "fscontext=u:object_r:tmpfs:s0," |
| "nosymfollow", |
| True) |
| |
| # Make the test fail if any unexpected behaviour got detected. Note |
| # that the error log output that will be included in the failure |
| # message mentions the failed location to aid debugging. |
| if self._failure: |
| raise error.TestFail('Unexpected mount behavior') |