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