普通文本  |  151行  |  5.43 KB

# 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')