普通文本  |  364行  |  13.41 KB

# Copyright 2014-2015, Tresys Technology, LLC
#
# This file is part of SETools.
#
# SETools is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 2.1 of
# the License, or (at your option) any later version.
#
# SETools is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with SETools.  If not, see
# <http://www.gnu.org/licenses/>.
#
import sys
import logging
from errno import ENOENT

from . import exception
from . import policyrep


class PermissionMap(object):

    """Permission Map for information flow analysis."""

    valid_infoflow_directions = ["r", "w", "b", "n", "u"]
    min_weight = 1
    max_weight = 10

    def __init__(self, permmapfile=None):
        """
        Parameter:
        permmapfile     The path to the permission map to load.
        """
        self.log = logging.getLogger(self.__class__.__name__)

        if permmapfile:
            self.load(permmapfile)
        else:
            for path in ["data/", sys.prefix + "/share/setools/"]:
                try:
                    self.load(path + "perm_map")
                    break
                except (IOError, OSError) as err:
                    if err.errno != ENOENT:
                        raise
            else:
                raise RuntimeError("Unable to load default permission map.")

    def load(self, permmapfile):
        """
        Parameter:
        permmapfile     The path to the permission map to load.
        """
        self.log.info("Opening permission map \"{0}\"".format(permmapfile))

        # state machine
        # 1 = read number of classes
        # 2 = read class name and number of perms
        # 3 = read perms
        with open(permmapfile, "r") as mapfile:
            class_count = 0
            num_classes = 0
            state = 1

            self.permmap = dict()

            for line_num, line in enumerate(mapfile, start=1):
                entry = line.split()

                if len(entry) == 0 or entry[0][0] == '#':
                    continue

                if state == 1:
                    try:
                        num_classes = int(entry[0])
                    except ValueError:
                        raise exception.PermissionMapParseError(
                            "{0}:{1}:Invalid number of classes: {2}".
                            format(permmapfile, line_num, entry[0]))

                    if num_classes < 1:
                        raise exception.PermissionMapParseError(
                            "{0}:{1}:Number of classes must be positive: {2}".
                            format(permmapfile, line_num, entry[0]))

                    state = 2

                elif state == 2:
                    if len(entry) != 3 or entry[0] != "class":
                        raise exception.PermissionMapParseError(
                            "{0}:{1}:Invalid class declaration: {2}".
                            format(permmapfile, line_num, entry))

                    class_name = str(entry[1])

                    try:
                        num_perms = int(entry[2])
                    except ValueError:
                        raise exception.PermissionMapParseError(
                            "{0}:{1}:Invalid number of permissions: {2}".
                            format(permmapfile, line_num, entry[2]))

                    if num_perms < 1:
                        raise exception.PermissionMapParseError(
                            "{0}:{1}:Number of permissions must be positive: {2}".
                            format(permmapfile, line_num, entry[2]))

                    class_count += 1
                    if class_count > num_classes:
                        raise exception.PermissionMapParseError(
                            "{0}:{1}:Extra class found: {2}".
                            format(permmapfile, line_num, class_name))

                    self.permmap[class_name] = dict()
                    perm_count = 0
                    state = 3

                elif state == 3:
                    perm_name = str(entry[0])

                    flow_direction = str(entry[1])
                    if flow_direction not in self.valid_infoflow_directions:
                        raise exception.PermissionMapParseError(
                            "{0}:{1}:Invalid information flow direction: {2}".
                            format(permmapfile, line_num, entry[1]))

                    try:
                        weight = int(entry[2])
                    except ValueError:
                        raise exception.PermissionMapParseError(
                            "{0}:{1}:Invalid permission weight: {2}".
                            format(permmapfile, line_num, entry[2]))

                    if not self.min_weight <= weight <= self.max_weight:
                        raise exception.PermissionMapParseError(
                            "{0}:{1}:Permission weight must be {3}-{4}: {2}".
                            format(permmapfile, line_num, entry[2],
                                   self.min_weight, self.max_weight))

                    self.permmap[class_name][perm_name] = {'direction': flow_direction,
                                                           'weight': weight,
                                                           'enabled': True}

                    perm_count += 1
                    if perm_count >= num_perms:
                        state = 2

    def exclude_class(self, class_):
        """
        Exclude all permissions in an object class for calculating rule weights.

        Parameter:
        class_              The object class to exclude.

        Exceptions:
        UnmappedClass       The specified object class is not mapped.
        """

        classname = str(class_)

        try:
            for perm in self.permmap[classname]:
                self.permmap[classname][perm]['enabled'] = False
        except KeyError:
            raise exception.UnmappedClass("{0} is not mapped.".format(classname))

    def exclude_permission(self, class_, permission):
        """
        Exclude a permission for calculating rule weights.

        Parameter:
        class_              The object class of the permission.
        permission          The permission name to exclude.

        Exceptions:
        UnmappedClass       The specified object class is not mapped.
        UnmappedPermission  The specified permission is not mapped for the object class.
        """
        classname = str(class_)

        if classname not in self.permmap:
            raise exception.UnmappedClass("{0} is not mapped.".format(classname))

        try:
            self.permmap[classname][permission]['enabled'] = False
        except KeyError:
            raise exception.UnmappedPermission("{0}:{1} is not mapped.".
                                               format(classname, permission))

    def include_class(self, class_):
        """
        Include all permissions in an object class for calculating rule weights.

        Parameter:
        class_              The object class to include.

        Exceptions:
        UnmappedClass       The specified object class is not mapped.
        """

        classname = str(class_)

        try:
            for perm in self.permmap[classname]:
                self.permmap[classname][perm]['enabled'] = True
        except KeyError:
            raise exception.UnmappedClass("{0} is not mapped.".format(classname))

    def include_permission(self, class_, permission):
        """
        Include a permission for calculating rule weights.

        Parameter:
        class_              The object class of the permission.
        permission          The permission name to include.

        Exceptions:
        UnmappedClass       The specified object class is not mapped.
        UnmappedPermission  The specified permission is not mapped for the object class.
        """

        classname = str(class_)

        if classname not in self.permmap:
            raise exception.UnmappedClass("{0} is not mapped.".format(classname))

        try:
            self.permmap[classname][permission]['enabled'] = True
        except KeyError:
            raise exception.UnmappedPermission("{0}:{1} is not mapped.".
                                               format(classname, permission))

    def map_policy(self, policy):
        """Create mappings for all classes and permissions in the specified policy."""
        for class_ in policy.classes():
            class_name = str(class_)

            if class_name not in self.permmap:
                self.log.info("Adding unmapped class {0} from {1}".format(class_name, policy))
                self.permmap[class_name] = dict()

            perms = class_.perms

            try:
                perms |= class_.common.perms
            except policyrep.exception.NoCommon:
                pass

            for perm_name in perms:
                if perm_name not in self.permmap[class_name]:
                    self.log.info("Adding unmapped permission {0} in {1} from {2}".
                                  format(perm_name, class_name, policy))
                    self.permmap[class_name][perm_name] = {'direction': 'u',
                                                           'weight': 1,
                                                           'enabled': True}

    def rule_weight(self, rule):
        """
        Get the type enforcement rule's information flow read and write weights.

        Parameter:
        rule            A type enforcement rule.

        Return: Tuple(read_weight, write_weight)
        read_weight     The type enforcement rule's read weight.
        write_weight    The type enforcement rule's write weight.
        """

        write_weight = 0
        read_weight = 0
        class_name = str(rule.tclass)

        if rule.ruletype != 'allow':
            raise exception.RuleTypeError("{0} rules cannot be used for calculating a weight".
                                          format(rule.ruletype))

        if class_name not in self.permmap:
            raise exception.UnmappedClass("{0} is not mapped.".format(class_name))

        # iterate over the permissions and determine the
        # weight of the rule in each direction. The result
        # is the largest-weight permission in each direction
        for perm_name in rule.perms:
            try:
                mapping = self.permmap[class_name][perm_name]
            except KeyError:
                raise exception.UnmappedPermission("{0}:{1} is not mapped.".
                                                   format(class_name, perm_name))

            if not mapping['enabled']:
                continue

            if mapping['direction'] == "r":
                read_weight = max(read_weight, mapping['weight'])
            elif mapping['direction'] == "w":
                write_weight = max(write_weight, mapping['weight'])
            elif mapping['direction'] == "b":
                read_weight = max(read_weight, mapping['weight'])
                write_weight = max(write_weight, mapping['weight'])

        return (read_weight, write_weight)

    def set_direction(self, class_, permission, direction):
        """
        Set the information flow direction of a permission.

        Parameter:
        class_              The object class of the permission.
        permission          The permission name.
        direction           The information flow direction the permission (r/w/b/n).

        Exceptions:
        UnmappedClass       The specified object class is not mapped.
        UnmappedPermission  The specified permission is not mapped for the object class.
        """

        if direction not in self.valid_infoflow_directions:
            raise ValueError("Invalid information flow direction: {0}".format(direction))

        classname = str(class_)

        if classname not in self.permmap:
            raise exception.UnmappedClass("{0} is not mapped.".format(classname))

        try:
            self.permmap[classname][permission]['direction'] = direction
        except KeyError:
            raise exception.UnmappedPermission("{0}:{1} is not mapped.".
                                               format(classname, permission))

    def set_weight(self, class_, permission, weight):
        """
        Set the weight of a permission.

        Parameter:
        class_              The object class of the permission.
        permission          The permission name.
        weight              The weight of the permission (1-10).

        Exceptions:
        UnmappedClass       The specified object class is not mapped.
        UnmappedPermission  The specified permission is not mapped for the object class.
        """

        if not self.min_weight <= weight <= self.max_weight:
            raise ValueError("Permission weights must be 1-10: {0}".format(weight))

        classname = str(class_)

        if classname not in self.permmap:
            raise exception.UnmappedClass("{0} is not mapped.".format(classname))

        try:
            self.permmap[classname][permission]['weight'] = weight
        except KeyError:
            raise exception.UnmappedPermission("{0}:{1} is not mapped.".
                                               format(classname, permission))