Home | History | Annotate | Download | only in setools
      1 # Copyright 2014-2015, Tresys Technology, LLC
      2 #
      3 # This file is part of SETools.
      4 #
      5 # SETools is free software: you can redistribute it and/or modify
      6 # it under the terms of the GNU Lesser General Public License as
      7 # published by the Free Software Foundation, either version 2.1 of
      8 # the License, or (at your option) any later version.
      9 #
     10 # SETools is distributed in the hope that it will be useful,
     11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 # GNU Lesser General Public License for more details.
     14 #
     15 # You should have received a copy of the GNU Lesser General Public
     16 # License along with SETools.  If not, see
     17 # <http://www.gnu.org/licenses/>.
     18 #
     19 import sys
     20 import logging
     21 from errno import ENOENT
     22 
     23 from . import exception
     24 from . import policyrep
     25 
     26 
     27 class PermissionMap(object):
     28 
     29     """Permission Map for information flow analysis."""
     30 
     31     valid_infoflow_directions = ["r", "w", "b", "n", "u"]
     32     min_weight = 1
     33     max_weight = 10
     34 
     35     def __init__(self, permmapfile=None):
     36         """
     37         Parameter:
     38         permmapfile     The path to the permission map to load.
     39         """
     40         self.log = logging.getLogger(self.__class__.__name__)
     41 
     42         if permmapfile:
     43             self.load(permmapfile)
     44         else:
     45             for path in ["data/", sys.prefix + "/share/setools/"]:
     46                 try:
     47                     self.load(path + "perm_map")
     48                     break
     49                 except (IOError, OSError) as err:
     50                     if err.errno != ENOENT:
     51                         raise
     52             else:
     53                 raise RuntimeError("Unable to load default permission map.")
     54 
     55     def load(self, permmapfile):
     56         """
     57         Parameter:
     58         permmapfile     The path to the permission map to load.
     59         """
     60         self.log.info("Opening permission map \"{0}\"".format(permmapfile))
     61 
     62         # state machine
     63         # 1 = read number of classes
     64         # 2 = read class name and number of perms
     65         # 3 = read perms
     66         with open(permmapfile, "r") as mapfile:
     67             class_count = 0
     68             num_classes = 0
     69             state = 1
     70 
     71             self.permmap = dict()
     72 
     73             for line_num, line in enumerate(mapfile, start=1):
     74                 entry = line.split()
     75 
     76                 if len(entry) == 0 or entry[0][0] == '#':
     77                     continue
     78 
     79                 if state == 1:
     80                     try:
     81                         num_classes = int(entry[0])
     82                     except ValueError:
     83                         raise exception.PermissionMapParseError(
     84                             "{0}:{1}:Invalid number of classes: {2}".
     85                             format(permmapfile, line_num, entry[0]))
     86 
     87                     if num_classes < 1:
     88                         raise exception.PermissionMapParseError(
     89                             "{0}:{1}:Number of classes must be positive: {2}".
     90                             format(permmapfile, line_num, entry[0]))
     91 
     92                     state = 2
     93 
     94                 elif state == 2:
     95                     if len(entry) != 3 or entry[0] != "class":
     96                         raise exception.PermissionMapParseError(
     97                             "{0}:{1}:Invalid class declaration: {2}".
     98                             format(permmapfile, line_num, entry))
     99 
    100                     class_name = str(entry[1])
    101 
    102                     try:
    103                         num_perms = int(entry[2])
    104                     except ValueError:
    105                         raise exception.PermissionMapParseError(
    106                             "{0}:{1}:Invalid number of permissions: {2}".
    107                             format(permmapfile, line_num, entry[2]))
    108 
    109                     if num_perms < 1:
    110                         raise exception.PermissionMapParseError(
    111                             "{0}:{1}:Number of permissions must be positive: {2}".
    112                             format(permmapfile, line_num, entry[2]))
    113 
    114                     class_count += 1
    115                     if class_count > num_classes:
    116                         raise exception.PermissionMapParseError(
    117                             "{0}:{1}:Extra class found: {2}".
    118                             format(permmapfile, line_num, class_name))
    119 
    120                     self.permmap[class_name] = dict()
    121                     perm_count = 0
    122                     state = 3
    123 
    124                 elif state == 3:
    125                     perm_name = str(entry[0])
    126 
    127                     flow_direction = str(entry[1])
    128                     if flow_direction not in self.valid_infoflow_directions:
    129                         raise exception.PermissionMapParseError(
    130                             "{0}:{1}:Invalid information flow direction: {2}".
    131                             format(permmapfile, line_num, entry[1]))
    132 
    133                     try:
    134                         weight = int(entry[2])
    135                     except ValueError:
    136                         raise exception.PermissionMapParseError(
    137                             "{0}:{1}:Invalid permission weight: {2}".
    138                             format(permmapfile, line_num, entry[2]))
    139 
    140                     if not self.min_weight <= weight <= self.max_weight:
    141                         raise exception.PermissionMapParseError(
    142                             "{0}:{1}:Permission weight must be {3}-{4}: {2}".
    143                             format(permmapfile, line_num, entry[2],
    144                                    self.min_weight, self.max_weight))
    145 
    146                     self.permmap[class_name][perm_name] = {'direction': flow_direction,
    147                                                            'weight': weight,
    148                                                            'enabled': True}
    149 
    150                     perm_count += 1
    151                     if perm_count >= num_perms:
    152                         state = 2
    153 
    154     def exclude_class(self, class_):
    155         """
    156         Exclude all permissions in an object class for calculating rule weights.
    157 
    158         Parameter:
    159         class_              The object class to exclude.
    160 
    161         Exceptions:
    162         UnmappedClass       The specified object class is not mapped.
    163         """
    164 
    165         classname = str(class_)
    166 
    167         try:
    168             for perm in self.permmap[classname]:
    169                 self.permmap[classname][perm]['enabled'] = False
    170         except KeyError:
    171             raise exception.UnmappedClass("{0} is not mapped.".format(classname))
    172 
    173     def exclude_permission(self, class_, permission):
    174         """
    175         Exclude a permission for calculating rule weights.
    176 
    177         Parameter:
    178         class_              The object class of the permission.
    179         permission          The permission name to exclude.
    180 
    181         Exceptions:
    182         UnmappedClass       The specified object class is not mapped.
    183         UnmappedPermission  The specified permission is not mapped for the object class.
    184         """
    185         classname = str(class_)
    186 
    187         if classname not in self.permmap:
    188             raise exception.UnmappedClass("{0} is not mapped.".format(classname))
    189 
    190         try:
    191             self.permmap[classname][permission]['enabled'] = False
    192         except KeyError:
    193             raise exception.UnmappedPermission("{0}:{1} is not mapped.".
    194                                                format(classname, permission))
    195 
    196     def include_class(self, class_):
    197         """
    198         Include all permissions in an object class for calculating rule weights.
    199 
    200         Parameter:
    201         class_              The object class to include.
    202 
    203         Exceptions:
    204         UnmappedClass       The specified object class is not mapped.
    205         """
    206 
    207         classname = str(class_)
    208 
    209         try:
    210             for perm in self.permmap[classname]:
    211                 self.permmap[classname][perm]['enabled'] = True
    212         except KeyError:
    213             raise exception.UnmappedClass("{0} is not mapped.".format(classname))
    214 
    215     def include_permission(self, class_, permission):
    216         """
    217         Include a permission for calculating rule weights.
    218 
    219         Parameter:
    220         class_              The object class of the permission.
    221         permission          The permission name to include.
    222 
    223         Exceptions:
    224         UnmappedClass       The specified object class is not mapped.
    225         UnmappedPermission  The specified permission is not mapped for the object class.
    226         """
    227 
    228         classname = str(class_)
    229 
    230         if classname not in self.permmap:
    231             raise exception.UnmappedClass("{0} is not mapped.".format(classname))
    232 
    233         try:
    234             self.permmap[classname][permission]['enabled'] = True
    235         except KeyError:
    236             raise exception.UnmappedPermission("{0}:{1} is not mapped.".
    237                                                format(classname, permission))
    238 
    239     def map_policy(self, policy):
    240         """Create mappings for all classes and permissions in the specified policy."""
    241         for class_ in policy.classes():
    242             class_name = str(class_)
    243 
    244             if class_name not in self.permmap:
    245                 self.log.info("Adding unmapped class {0} from {1}".format(class_name, policy))
    246                 self.permmap[class_name] = dict()
    247 
    248             perms = class_.perms
    249 
    250             try:
    251                 perms |= class_.common.perms
    252             except policyrep.exception.NoCommon:
    253                 pass
    254 
    255             for perm_name in perms:
    256                 if perm_name not in self.permmap[class_name]:
    257                     self.log.info("Adding unmapped permission {0} in {1} from {2}".
    258                                   format(perm_name, class_name, policy))
    259                     self.permmap[class_name][perm_name] = {'direction': 'u',
    260                                                            'weight': 1,
    261                                                            'enabled': True}
    262 
    263     def rule_weight(self, rule):
    264         """
    265         Get the type enforcement rule's information flow read and write weights.
    266 
    267         Parameter:
    268         rule            A type enforcement rule.
    269 
    270         Return: Tuple(read_weight, write_weight)
    271         read_weight     The type enforcement rule's read weight.
    272         write_weight    The type enforcement rule's write weight.
    273         """
    274 
    275         write_weight = 0
    276         read_weight = 0
    277         class_name = str(rule.tclass)
    278 
    279         if rule.ruletype != 'allow':
    280             raise exception.RuleTypeError("{0} rules cannot be used for calculating a weight".
    281                                           format(rule.ruletype))
    282 
    283         if class_name not in self.permmap:
    284             raise exception.UnmappedClass("{0} is not mapped.".format(class_name))
    285 
    286         # iterate over the permissions and determine the
    287         # weight of the rule in each direction. The result
    288         # is the largest-weight permission in each direction
    289         for perm_name in rule.perms:
    290             try:
    291                 mapping = self.permmap[class_name][perm_name]
    292             except KeyError:
    293                 raise exception.UnmappedPermission("{0}:{1} is not mapped.".
    294                                                    format(class_name, perm_name))
    295 
    296             if not mapping['enabled']:
    297                 continue
    298 
    299             if mapping['direction'] == "r":
    300                 read_weight = max(read_weight, mapping['weight'])
    301             elif mapping['direction'] == "w":
    302                 write_weight = max(write_weight, mapping['weight'])
    303             elif mapping['direction'] == "b":
    304                 read_weight = max(read_weight, mapping['weight'])
    305                 write_weight = max(write_weight, mapping['weight'])
    306 
    307         return (read_weight, write_weight)
    308 
    309     def set_direction(self, class_, permission, direction):
    310         """
    311         Set the information flow direction of a permission.
    312 
    313         Parameter:
    314         class_              The object class of the permission.
    315         permission          The permission name.
    316         direction           The information flow direction the permission (r/w/b/n).
    317 
    318         Exceptions:
    319         UnmappedClass       The specified object class is not mapped.
    320         UnmappedPermission  The specified permission is not mapped for the object class.
    321         """
    322 
    323         if direction not in self.valid_infoflow_directions:
    324             raise ValueError("Invalid information flow direction: {0}".format(direction))
    325 
    326         classname = str(class_)
    327 
    328         if classname not in self.permmap:
    329             raise exception.UnmappedClass("{0} is not mapped.".format(classname))
    330 
    331         try:
    332             self.permmap[classname][permission]['direction'] = direction
    333         except KeyError:
    334             raise exception.UnmappedPermission("{0}:{1} is not mapped.".
    335                                                format(classname, permission))
    336 
    337     def set_weight(self, class_, permission, weight):
    338         """
    339         Set the weight of a permission.
    340 
    341         Parameter:
    342         class_              The object class of the permission.
    343         permission          The permission name.
    344         weight              The weight of the permission (1-10).
    345 
    346         Exceptions:
    347         UnmappedClass       The specified object class is not mapped.
    348         UnmappedPermission  The specified permission is not mapped for the object class.
    349         """
    350 
    351         if not self.min_weight <= weight <= self.max_weight:
    352             raise ValueError("Permission weights must be 1-10: {0}".format(weight))
    353 
    354         classname = str(class_)
    355 
    356         if classname not in self.permmap:
    357             raise exception.UnmappedClass("{0} is not mapped.".format(classname))
    358 
    359         try:
    360             self.permmap[classname][permission]['weight'] = weight
    361         except KeyError:
    362             raise exception.UnmappedPermission("{0}:{1} is not mapped.".
    363                                                format(classname, permission))
    364