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