Home | History | Annotate | Download | only in src
      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