1 import pdb 2 import re 3 from xml.etree.ElementTree import Element, SubElement, tostring 4 5 #define equivalents 6 TYPE = 0 7 ATTRIBUTE = 1 8 TYPEATTRIBUTE = 2 9 CLASS = 3 10 COMMON = 4 11 ALLOW_RULE = 5 12 NEVERALLOW_RULE = 6 13 OTHER = 7 14 15 #define helper methods 16 # advance_past_whitespace(): helper function to skip whitespace at current 17 # position in file. 18 # returns: the non-whitespace character at the file's new position 19 #TODO: should I deal with comments here as well? 20 def advance_past_whitespace(file_obj): 21 c = file_obj.read(1) 22 while c.isspace(): 23 c = file_obj.read(1) 24 file_obj.seek(-1, 1) 25 return c 26 27 # advance_until_whitespace(): helper function to grab the string represented 28 # by the current position in file until next whitespace. 29 # returns: string until next whitespace. overlooks comments. 30 def advance_until_whitespace(file_obj): 31 ret_string = "" 32 c = file_obj.read(1) 33 #TODO: make a better way to deal with ':' and ';' 34 while not (c.isspace() or c == ':' or c == '' or c == ';'): 35 #don't count comments 36 if c == '#': 37 file_obj.readline() 38 return ret_string 39 else: 40 ret_string+=c 41 c = file_obj.read(1) 42 if not c == ':': 43 file_obj.seek(-1, 1) 44 return ret_string 45 46 # expand_avc_rule - takes a processed avc rule and converts it into a list of 47 # 4-tuples for use in an access check of form: 48 # (source_type, target_type, class, permission) 49 def expand_avc_rule(policy, avc_rule): 50 ret_list = [ ] 51 52 #expand source_types 53 source_types = avc_rule['source_types']['set'] 54 source_types = policy.expand_types(source_types) 55 if(avc_rule['source_types']['flags']['complement']): 56 #TODO: deal with negated 'self', not present in current policy.conf, though (I think) 57 source_types = policy.types - source_types #complement these types 58 if len(source_types) == 0: 59 print "ERROR: source_types empty after expansion" 60 print "Before: " 61 print avc_rule['source_types']['set'] 62 return 63 64 #expand target_types 65 target_types = avc_rule['target_types']['set'] 66 target_types = policy.expand_types(target_types) 67 if(avc_rule['target_types']['flags']['complement']): 68 #TODO: deal with negated 'self', not present in current policy.conf, though (I think) 69 target_types = policy.types - target_types #complement these types 70 if len(target_types) == 0: 71 print "ERROR: target_types empty after expansion" 72 print "Before: " 73 print avc_rule['target_types']['set'] 74 return 75 76 # get classes 77 rule_classes = avc_rule['classes']['set'] 78 if '' in rule_classes: 79 print "FOUND EMPTY STRING IN CLASSES" 80 print "Total sets:" 81 print avc_rule['source_types']['set'] 82 print avc_rule['target_types']['set'] 83 print rule_classes 84 print avc_rule['permissions']['set'] 85 86 if len(rule_classes) == 0: 87 print "ERROR: empy set of object classes in avc rule" 88 return 89 90 # get permissions 91 permissions = avc_rule['permissions']['set'] 92 if len(permissions) == 0: 93 print "ERROR: empy set of permissions in avc rule\n" 94 return 95 96 #create the list with collosal nesting, n^4 baby! 97 for s in source_types: 98 for t in target_types: 99 for c in rule_classes: 100 if c == '': 101 continue 102 #expand permissions on a per-class basis 103 exp_permissions = policy.expand_permissions(c, permissions) 104 if(avc_rule['permissions']['flags']['complement']): 105 exp_permissions = policy.classes[c] - exp_permissions 106 if len(exp_permissions) == 0: 107 print "ERROR: permissions empty after expansion\n" 108 print "Before: " 109 print avc_rule['permissions']['set'] 110 return 111 for p in exp_permissions: 112 source = s 113 if t == 'self': 114 target = s 115 else: 116 target = t 117 obj_class = c 118 permission = p 119 ret_list.append((source, target, obj_class, permission)) 120 return ret_list 121 122 # expand_avc_rule - takes a processed avc rule and converts it into an xml 123 # representation with the information needed in a checkSELinuxAccess() call. 124 # (source_type, target_type, class, permission) 125 def expand_avc_rule_to_xml(policy, avc_rule, rule_name, rule_type): 126 rule_xml = Element('avc_rule') 127 rule_xml.set('name', rule_name) 128 rule_xml.set('type', rule_type) 129 130 #expand source_types 131 source_types = avc_rule['source_types']['set'] 132 source_types = policy.expand_types(source_types) 133 if(avc_rule['source_types']['flags']['complement']): 134 #TODO: deal with negated 'self', not present in current policy.conf, though (I think) 135 source_types = policy.types - source_types #complement these types 136 if len(source_types) == 0: 137 print "ERROR: source_types empty after expansion" 138 print "Before: " 139 print avc_rule['source_types']['set'] 140 return 141 for s in source_types: 142 elem = SubElement(rule_xml, 'type') 143 elem.set('type', 'source') 144 elem.text = s 145 146 #expand target_types 147 target_types = avc_rule['target_types']['set'] 148 target_types = policy.expand_types(target_types) 149 if(avc_rule['target_types']['flags']['complement']): 150 #TODO: deal with negated 'self', not present in current policy.conf, though (I think) 151 target_types = policy.types - target_types #complement these types 152 if len(target_types) == 0: 153 print "ERROR: target_types empty after expansion" 154 print "Before: " 155 print avc_rule['target_types']['set'] 156 return 157 for t in target_types: 158 elem = SubElement(rule_xml, 'type') 159 elem.set('type', 'target') 160 elem.text = t 161 162 # get classes 163 rule_classes = avc_rule['classes']['set'] 164 165 if len(rule_classes) == 0: 166 print "ERROR: empy set of object classes in avc rule" 167 return 168 169 # get permissions 170 permissions = avc_rule['permissions']['set'] 171 if len(permissions) == 0: 172 print "ERROR: empy set of permissions in avc rule\n" 173 return 174 175 # permissions are class-dependent, so bundled together 176 for c in rule_classes: 177 if c == '': 178 print "AH!!! empty class found!\n" 179 continue 180 c_elem = SubElement(rule_xml, 'obj_class') 181 c_elem.set('name', c) 182 #expand permissions on a per-class basis 183 exp_permissions = policy.expand_permissions(c, permissions) 184 if(avc_rule['permissions']['flags']['complement']): 185 exp_permissions = policy.classes[c] - exp_permissions 186 if len(exp_permissions) == 0: 187 print "ERROR: permissions empty after expansion\n" 188 print "Before: " 189 print avc_rule['permissions']['set'] 190 return 191 192 for p in exp_permissions: 193 p_elem = SubElement(c_elem, 'permission') 194 p_elem.text = p 195 196 return rule_xml 197 198 # expand_brackets - helper function which reads a file into a string until '{ }'s 199 # are balanced. Brackets are removed from the string. This function is based 200 # on the understanding that nested brackets in our policy.conf file occur only due 201 # to macro expansion, and we just need to know how much is included in a given 202 # policy sub-component. 203 def expand_brackets(file_obj): 204 ret_string = "" 205 c = file_obj.read(1) 206 if not c == '{': 207 print "Invalid bracket expression: " + c + "\n" 208 file_obj.seek(-1, 1) 209 return "" 210 else: 211 bracket_count = 1 212 while bracket_count > 0: 213 c = file_obj.read(1) 214 if c == '{': 215 bracket_count+=1 216 elif c == '}': 217 bracket_count-=1 218 elif c == '#': 219 #get rid of comment and replace with whitespace 220 file_obj.readline() 221 ret_string+=' ' 222 else: 223 ret_string+=c 224 return ret_string 225 226 # get_avc_rule_component - grabs the next component from an avc rule. Basically, 227 # just reads the next word or bracketed set of words. 228 # returns - a set of the word, or words with metadata 229 def get_avc_rule_component(file_obj): 230 ret_dict = { 'flags': {}, 'set': set() } 231 c = advance_past_whitespace(file_obj) 232 if c == '~': 233 ret_dict['flags']['complement'] = True 234 file_obj.read(1) #move to next char 235 c = advance_past_whitespace(file_obj) 236 else: 237 ret_dict['flags']['complement'] = False 238 if not c == '{': 239 #TODO: change operations on file to operations on string? 240 single_type = advance_until_whitespace(file_obj) 241 ret_dict['set'].add(single_type) 242 else: 243 mult_types = expand_brackets(file_obj) 244 mult_types = mult_types.split() 245 for t in mult_types: 246 ret_dict['set'].add(t) 247 return ret_dict 248 249 def get_line_type(line): 250 if re.search(r'^type\s', line): 251 return TYPE 252 if re.search(r'^attribute\s', line): 253 return ATTRIBUTE 254 if re.search(r'^typeattribute\s', line): 255 return TYPEATTRIBUTE 256 if re.search(r'^class\s', line): 257 return CLASS 258 if re.search(r'^common\s', line): 259 return COMMON 260 if re.search(r'^allow\s', line): 261 return ALLOW_RULE 262 if re.search(r'^neverallow\s', line): 263 return NEVERALLOW_RULE 264 else: 265 return OTHER 266 267 def is_multi_line(line_type): 268 if line_type == CLASS: 269 return True 270 elif line_type == COMMON: 271 return True 272 elif line_type == ALLOW_RULE: 273 return True 274 elif line_type == NEVERALLOW_RULE: 275 return True 276 else: 277 return False 278 279 280 #should only be called with file pointing to the 'i' in 'inherits' segment 281 def process_inherits_segment(file_obj): 282 inherit_keyword = file_obj.read(8) 283 if not inherit_keyword == 'inherits': 284 #TODO: handle error, invalid class statement 285 print "ERROR: invalid inherits statement" 286 return 287 else: 288 advance_past_whitespace(file_obj) 289 ret_inherited_common = advance_until_whitespace(file_obj) 290 return ret_inherited_common 291 292 class SELinuxPolicy: 293 294 def __init__(self): 295 self.types = set() 296 self.attributes = { } 297 self.classes = { } 298 self.common_classes = { } 299 self.allow_rules = [ ] 300 self.neverallow_rules = [ ] 301 302 # create policy directly from policy file 303 #@classmethod 304 def from_file_name(self, policy_file_name): 305 self.types = set() 306 self.attributes = { } 307 self.classes = { } 308 self.common_classes = { } 309 self.allow_rules = [ ] 310 self.neverallow_rules = [ ] 311 with open(policy_file_name, 'r') as policy_file: 312 line = policy_file.readline() 313 while line: 314 line_type = get_line_type(line) 315 if is_multi_line(line_type): 316 self.parse_multi_line(line, line_type, policy_file) 317 else: 318 self.parse_single_line(line, line_type) 319 line = policy_file.readline() 320 321 # expand_permissions - generates the actual permission set based on the listed 322 # permissions with wildcards and the given class on which they're based. 323 def expand_permissions(self, obj_class, permission_set): 324 ret_set = set() 325 neg_set = set() 326 for p in permission_set: 327 if p[0] == '-': 328 real_p = p[1:] 329 if real_p in self.classes[obj_class]: 330 neg_set.add(real_p) 331 else: 332 print "ERROR: invalid permission in avc rule " + real_t + "\n" 333 return 334 else: 335 if p in self.classes[obj_class]: 336 ret_set.add(p) 337 elif p == '*': #pretty sure this can't be negated? eg -* 338 ret_set |= self.classes[obj_class] #All of the permissions 339 else: 340 print "ERROR: invalid permission in avc rule " + p + "\n" 341 return 342 return ret_set - neg_set 343 344 # expand_types - generates the actual type set based on the listed types, 345 # attributes, wildcards and negation. self is left as-is, and is processed 346 # specially when generating checkAccess() 4-tuples 347 def expand_types(self, type_set): 348 ret_set = set() 349 neg_set = set() 350 for t in type_set: 351 if t[0] == '-': 352 real_t = t[1:] 353 if real_t in self.attributes: 354 neg_set |= self.attributes[real_t] 355 elif real_t in self.types: 356 neg_set.add(real_t) 357 elif real_t == 'self': 358 ret_set |= real_t 359 else: 360 print "ERROR: invalid type in avc rule " + real_t + "\nTYPE SET:" 361 print type_set 362 return 363 else: 364 if t in self.attributes: 365 ret_set |= self.attributes[t] 366 elif t in self.types: 367 ret_set.add(t) 368 elif t == 'self': 369 ret_set.add(t) 370 elif t == '*': #pretty sure this can't be negated? 371 ret_set |= self.types #All of the types 372 else: 373 print "ERROR: invalid type in avc rule " + t + "\nTYPE SET" 374 print type_set 375 return 376 return ret_set - neg_set 377 378 def parse_multi_line(self, line, line_type, file_obj): 379 if line_type == CLASS: 380 self.process_class_line(line, file_obj) 381 elif line_type == COMMON: 382 self.process_common_line(line, file_obj) 383 elif line_type == ALLOW_RULE: 384 self.process_avc_rule_line(line, file_obj) 385 elif line_type == NEVERALLOW_RULE: 386 self.process_avc_rule_line(line, file_obj) 387 else: 388 print "Error: This is not a multi-line input" 389 390 def parse_single_line(self, line, line_type): 391 if line_type == TYPE: 392 self.process_type_line(line) 393 elif line_type == ATTRIBUTE: 394 self.process_attribute_line(line) 395 elif line_type == TYPEATTRIBUTE: 396 self.process_typeattribute_line(line) 397 return 398 399 def process_attribute_line(self, line): 400 match = re.search(r'^attribute\s+(.+);', line) 401 if match: 402 declared_attribute = match.group(1) 403 self.attributes[declared_attribute] = set() 404 else: 405 #TODO: handle error? (no state changed) 406 return 407 408 def process_class_line(self, line, file_obj): 409 match = re.search(r'^class\s([^\s]+)\s(.*$)', line) 410 if match: 411 declared_class = match.group(1) 412 #first class declaration has no perms 413 if not declared_class in self.classes: 414 self.classes[declared_class] = set() 415 return 416 else: 417 #need to parse file from after class name until end of '{ }'s 418 file_obj.seek(-(len(match.group(2)) + 1), 1) 419 c = advance_past_whitespace(file_obj) 420 if not (c == 'i' or c == '{'): 421 print "ERROR: invalid class statement" 422 return 423 elif c == 'i': 424 #add inherited permissions 425 inherited = process_inherits_segment(file_obj) 426 self.classes[declared_class] |= self.common_classes[inherited] 427 c = advance_past_whitespace(file_obj) 428 if c == '{': 429 permissions = expand_brackets(file_obj) 430 permissions = re.sub(r'#[^\n]*\n','\n' , permissions) #get rid of all comments 431 permissions = permissions.split() 432 for p in permissions: 433 self.classes[declared_class].add(p) 434 435 def process_common_line(self, line, file_obj): 436 match = re.search(r'^common\s([^\s]+)(.*$)', line) 437 if match: 438 declared_common_class = match.group(1) 439 #TODO: common classes should only be declared once... 440 if not declared_common_class in self.common_classes: 441 self.common_classes[declared_common_class] = set() 442 #need to parse file from after common_class name until end of '{ }'s 443 file_obj.seek(-(len(match.group(2)) + 1), 1) 444 c = advance_past_whitespace(file_obj) 445 if not c == '{': 446 print "ERROR: invalid common statement" 447 return 448 permissions = expand_brackets(file_obj) 449 permissions = permissions.split() 450 for p in permissions: 451 self.common_classes[declared_common_class].add(p) 452 return 453 454 def process_avc_rule_line(self, line, file_obj): 455 match = re.search(r'^(never)?allow\s(.*$)', line) 456 if match: 457 if(match.group(1)): 458 rule_type = 'neverallow' 459 else: 460 rule_type = 'allow' 461 #need to parse file from after class name until end of '{ }'s 462 file_obj.seek(-(len(match.group(2)) + 1), 1) 463 464 #grab source type(s) 465 source_types = get_avc_rule_component(file_obj) 466 if len(source_types['set']) == 0: 467 print "ERROR: no source types for avc rule at line: " + line 468 return 469 470 #grab target type(s) 471 target_types = get_avc_rule_component(file_obj) 472 if len(target_types['set']) == 0: 473 print "ERROR: no target types for avc rule at line: " + line 474 return 475 476 #skip ':' potentially already handled by advance_until_whitespace 477 c = advance_past_whitespace(file_obj) 478 if c == ':': 479 file_obj.read(1) 480 481 #grab class(es) 482 classes = get_avc_rule_component(file_obj) 483 if len(classes['set']) == 0: 484 print "ERROR: no classes for avc rule at line: " + line 485 return 486 487 #grab permission(s) 488 permissions = get_avc_rule_component(file_obj) 489 if len(permissions['set']) == 0: 490 print "ERROR: no permissions for avc rule at line: " + line 491 return 492 rule_dict = { 493 'source_types': source_types, 494 'target_types': target_types, 495 'classes': classes, 496 'permissions': permissions } 497 498 if rule_type == 'allow': 499 self.allow_rules.append(rule_dict) 500 elif rule_type == 'neverallow': 501 self.neverallow_rules.append(rule_dict) 502 503 def process_type_line(self, line): 504 #TODO: add support for aliases (not yet in current policy.conf) 505 match = re.search(r'^type\s([^,]+),?(.*);', line) 506 if match: 507 declared_type = match.group(1) 508 self.types.add(declared_type) 509 if match.group(2): 510 declared_attributes = match.group(2) 511 declared_attributes = declared_attributes.replace(" ", "") #remove whitespace 512 declared_attributes = declared_attributes.split(',') #separate based on delimiter 513 for a in declared_attributes: 514 if not a in self.attributes: 515 #TODO: hanlde error? attribute should already exist 516 self.attributes[a] = set() 517 self.attributes[a].add(declared_type) 518 else: 519 #TODO: handle error? (no state changed) 520 return 521 522 def process_typeattribute_line(self, line): 523 match = re.search(r'^typeattribute\s([^\s]+)\s(.*);', line) 524 if match: 525 declared_type = match.group(1) 526 if not declared_type in self.types: 527 #TODO: handle error? type should already exist 528 self.types.add(declared_type) 529 if match.group(2): 530 declared_attributes = match.group(2) 531 declared_attributes = declared_attributes.replace(" ", "") #remove whitespace 532 declared_attributes = declared_attributes.split(',') #separate based on delimiter 533 for a in declared_attributes: 534 if not a in self.attributes: 535 #TODO: hanlde error? attribute should already exist 536 self.attributes[a] = set() 537 self.attributes[a].add(declared_type) 538 else: 539 return 540 else: 541 #TODO: handle error? (no state changed) 542 return 543