Пример #1
0
def parse_modifiers(matches):
    '''returns audit, deny, allow_keyword and comment from the matches object
       - audit, deny and allow_keyword are True/False
       - comment is the comment with a leading space'''
    audit = False
    if matches.group('audit'):
        audit = True

    deny = False
    allow_keyword = False

    allowstr = matches.group('allow')
    if allowstr:
        if allowstr.strip() == 'allow':
            allow_keyword = True
        elif allowstr.strip() == 'deny':
            deny = True
        else:
            raise AppArmorBug("Invalid allow/deny keyword %s" % allowstr)

    comment = parse_comment(matches)

    return (audit, deny, allow_keyword, comment)
Пример #2
0
    def _is_covered_list(self,
                         self_value,
                         self_all,
                         other_value,
                         other_all,
                         cond_name,
                         sanity_check=True):
        '''check if other_* is covered by self_* - for lists'''

        if sanity_check and not other_value and not other_all:
            raise AppArmorBug(
                'No %(cond_name)s specified in other %(rule_name)s rule' % {
                    'cond_name': cond_name,
                    'rule_name': self.rule_name
                })

        if not self_all:
            if other_all:
                return False
            if not other_value.issubset(self_value):
                return False

        # still here? -> then it is covered
        return True
Пример #3
0
    def __init__(self,
                 path,
                 ifexists,
                 ismagic,
                 audit=False,
                 deny=False,
                 allow_keyword=False,
                 comment='',
                 log_event=None):

        super(IncludeRule, self).__init__(audit=audit,
                                          deny=deny,
                                          allow_keyword=allow_keyword,
                                          comment=comment,
                                          log_event=log_event)

        # include doesn't support audit or deny
        if audit:
            raise AppArmorBug('Attempt to initialize %s with audit flag' %
                              self.__class__.__name__)
        if deny:
            raise AppArmorBug('Attempt to initialize %s with deny flag' %
                              self.__class__.__name__)

        if type(ifexists) is not bool:
            raise AppArmorBug('Passed unknown type for ifexists to %s: %s' %
                              (self.__class__.__name__, ifexists))
        if type(ismagic) is not bool:
            raise AppArmorBug('Passed unknown type for ismagic to %s: %s' %
                              (self.__class__.__name__, ismagic))
        if not type_is_str(path):
            raise AppArmorBug('Passed unknown type for path to %s: %s' %
                              (self.__class__.__name__, path))
        if not path:
            raise AppArmorBug('Passed empty path to %s: %s' %
                              (self.__class__.__name__, path))

        self.path = path
        self.ifexists = ifexists
        self.ismagic = ismagic
    def parse_event_for_tree(self, e):
        aamode = e.get('aamode', 'UNKNOWN')

        if aamode == 'UNKNOWN':
            raise AppArmorBug('aamode is UNKNOWN - %s' %
                              e['type'])  # should never happen

        if aamode in ['AUDIT', 'STATUS', 'ERROR']:
            return None

        if 'profile_set' in e['operation']:
            return None

        # Skip if AUDIT event was issued due to a change_hat in unconfined mode
        if not e.get('profile', False):
            return None

        # Convert new null profiles to old single level null profile
        if '//null-' in e['profile']:
            e['profile'] = 'null-complain-profile'

        profile = e['profile']
        hat = None

        if '//' in e['profile']:
            profile, hat = e['profile'].split('//')[:2]

        # Filter out change_hat events that aren't from learning
        if e['operation'] == 'change_hat':
            if aamode != 'HINT' and aamode != 'PERMITTING':
                return None
            if e['error_code'] == 1 and e[
                    'info'] == 'unconfined can not change_hat':
                return None
            profile = e['name2']
            #hat = None
            if '//' in e['name2']:
                profile, hat = e['name2'].split('//')[:2]

        if not hat:
            hat = profile

        # prog is no longer passed around consistently
        prog = 'HINT'

        if profile != 'null-complain-profile' and not self.profile_exists(
                profile):
            return None
        if e['operation'] == 'exec':
            # convert rmask and dmask to mode arrays
            e['denied_mask'], e['name2'] = log_str_to_mode(
                e['profile'], e['denied_mask'], e['name2'])
            e['request_mask'], e['name2'] = log_str_to_mode(
                e['profile'], e['request_mask'], e['name2'])

            if e.get('info',
                     False) and e['info'] == 'mandatory profile missing':
                return (e['pid'], e['parent'], 'exec', [
                    profile, hat, aamode, 'PERMITTING', e['denied_mask'],
                    e['name'], e['name2']
                ])
            elif (e.get('name2', False) and '//null-' in e['name2']) or e.get(
                    'name', False):
                return (e['pid'], e['parent'], 'exec', [
                    profile, hat, prog, aamode, e['denied_mask'], e['name'], ''
                ])
            else:
                self.debug_logger.debug(
                    'parse_event_for_tree: dropped exec event in %s' %
                    e['profile'])

        elif self.op_type(e) == 'file':
            # Map c (create) and d (delete) to w (logging is more detailed than the profile language)
            rmask = e['request_mask']
            rmask = rmask.replace('c', 'w')
            rmask = rmask.replace('d', 'w')
            if not validate_log_mode(hide_log_mode(rmask)):
                raise AppArmorException(
                    _('Log contains unknown mode %s') % rmask)

            dmask = e['denied_mask']
            dmask = dmask.replace('c', 'w')
            dmask = dmask.replace('d', 'w')
            if not validate_log_mode(hide_log_mode(dmask)):
                raise AppArmorException(
                    _('Log contains unknown mode %s') % dmask)

            if e.get('ouid') is not None and e['fsuid'] == e['ouid']:
                # mark as "owner" event
                if '::' not in rmask:
                    rmask = '%s::' % rmask
                if '::' not in dmask:
                    dmask = '%s::' % dmask

            # convert rmask and dmask to mode arrays
            e['denied_mask'], e['name2'] = log_str_to_mode(
                e['profile'], dmask, e['name2'])
            e['request_mask'], e['name2'] = log_str_to_mode(
                e['profile'], rmask, e['name2'])

            # check if this is an exec event
            is_domain_change = False
            if e['operation'] == 'inode_permission' and (
                    e['denied_mask'] & AA_MAY_EXEC) and aamode == 'PERMITTING':
                following = self.peek_at_next_log_entry()
                if following:
                    entry = self.parse_log_record(following)
                    if entry and entry.get('info', False) == 'set profile':
                        is_domain_change = True
                        self.throw_away_next_log_entry()

            if is_domain_change:
                return (e['pid'], e['parent'], 'exec', [
                    profile, hat, prog, aamode, e['denied_mask'], e['name'],
                    e['name2']
                ])
            else:
                return (e['pid'], e['parent'], 'path', [
                    profile, hat, prog, aamode, e['denied_mask'], e['name'], ''
                ])

        elif e['operation'] == 'capable':
            return (e['pid'], e['parent'], 'capability',
                    [profile, hat, prog, aamode, e['name'], ''])

        elif e['operation'] == 'clone':
            parent, child = e['pid'], e['task']
            if not parent:
                parent = 'null-complain-profile'
            if not hat:
                hat = 'null-complain-profile'
            arrayref = []
            if self.pid.get(parent, False):
                self.pid[parent].append(arrayref)
            else:
                self.log.append(arrayref)
            self.pid[child].append(arrayref)
            for ia in ['fork', child, profile, hat]:
                arrayref.append(ia)
#             if self.pid.get(parent, False):
#                 self.pid[parent] += [arrayref]
#             else:
#                 self.log += [arrayref]
#             self.pid[child] = arrayref

        elif self.op_type(e) == 'net':
            return (e['pid'], e['parent'], 'netdomain', [
                profile, hat, prog, aamode, e['family'], e['sock_type'],
                e['protocol']
            ])
        elif e['operation'] == 'change_hat':
            return (e['pid'], e['parent'], 'unknown_hat',
                    [profile, hat, aamode, hat])
        elif e['operation'] == 'ptrace':
            if not e['peer']:
                self.debug_logger.debug(
                    'ignored garbage ptrace event with empty peer')
                return None
            if not e['denied_mask']:
                self.debug_logger.debug(
                    'ignored garbage ptrace event with empty denied_mask')
                return None

            return (e['pid'], e['parent'], 'ptrace',
                    [profile, hat, prog, aamode, e['denied_mask'], e['peer']])
        elif e['operation'] == 'signal':
            return (e['pid'], e['parent'], 'signal', [
                profile, hat, prog, aamode, e['denied_mask'], e['signal'],
                e['peer']
            ])
        elif e['operation'].startswith('dbus_'):
            return (e['pid'], e['parent'], 'dbus', [
                profile, hat, prog, aamode, e['denied_mask'], e['bus'],
                e['path'], e['name'], e['interface'], e['member'],
                e['peer_profile']
            ])
        else:
            self.debug_logger.debug('UNHANDLED: %s' % e)
Пример #5
0
 def get(self, key, fallback=None):
     if key in self.data:
         return self.data.get(key, fallback)
     else:
         raise AppArmorBug('attempt to read unknown key %s' % key)
Пример #6
0
 def __getitem__(self, key):
     if key in self.data:
         return self.data[key]
     else:
         raise AppArmorBug('attempt to read unknown key %s' % key)
Пример #7
0
    def parse_event_for_tree(self, e):
        aamode = e.get('aamode', 'UNKNOWN')

        if aamode == 'UNKNOWN':
            raise AppArmorBug('aamode is UNKNOWN - %s' %
                              e['type'])  # should never happen

        if aamode in ['AUDIT', 'STATUS', 'ERROR']:
            return None

        # Skip if AUDIT event was issued due to a change_hat in unconfined mode
        if not e.get('profile', False):
            return None

        full_profile = e['profile']  # full, nested profile name
        self.init_hashlog(aamode, full_profile)

        # Convert new null profiles to old single level null profile
        if '//null-' in e['profile']:
            e['profile'] = 'null-complain-profile'

        profile, hat = split_name(e['profile'])

        if profile != 'null-complain-profile' and not self.profile_exists(
                profile):
            return None
        if e['operation'] == 'exec':
            if not e['name']:
                raise AppArmorException('exec without executed binary')

            if not e['name2']:
                e['name2'] = ''  # exec events in enforce mode don't have target=...

            self.hashlog[aamode][full_profile]['exec'][e['name']][
                e['name2']] = True
            return None

        elif self.op_type(e) == 'file':
            # Map c (create) and d (delete) to w (logging is more detailed than the profile language)
            dmask = e['denied_mask']
            dmask = dmask.replace('c', 'w')
            dmask = dmask.replace('d', 'w')

            owner = False

            if '::' in dmask:
                # old log styles used :: to indicate if permissions are meant for owner or other
                (owner_d, other_d) = dmask.split('::')
                if owner_d and other_d:
                    raise AppArmorException(
                        'Found log event with both owner and other permissions. Please open a bugreport!'
                    )
                if owner_d:
                    dmask = owner_d
                    owner = True
                else:
                    dmask = other_d

            if e.get('ouid') is not None and e['fsuid'] == e['ouid']:
                # in current log style, owner permissions are indicated by a match of fsuid and ouid
                owner = True

            for perm in dmask:
                if perm in 'mrwalk':  # intentionally not allowing 'x' here
                    self.hashlog[aamode][full_profile]['path'][
                        e['name']][owner][perm] = True
                else:
                    raise AppArmorException(
                        _('Log contains unknown mode %s') % dmask)

            return None

        elif e['operation'] == 'capable':
            self.hashlog[aamode][full_profile]['capability'][e['name']] = True
            return None

        elif self.op_type(e) == 'net':
            self.hashlog[aamode][full_profile]['network'][e['family']][
                e['sock_type']][e['protocol']] = True
            return None

        elif e['operation'] == 'change_hat':
            if e['error_code'] == 1 and e[
                    'info'] == 'unconfined can not change_hat':
                return None

            self.hashlog[aamode][full_profile]['change_hat'][e['name2']] = True
            return None

        elif e['operation'] == 'change_profile':
            self.hashlog[aamode][full_profile]['change_profile'][
                e['name2']] = True
            return None

        elif e['operation'] == 'ptrace':
            if not e['peer']:
                self.debug_logger.debug(
                    'ignored garbage ptrace event with empty peer')
                return None
            if not e['denied_mask']:
                self.debug_logger.debug(
                    'ignored garbage ptrace event with empty denied_mask')
                return None

            self.hashlog[aamode][full_profile]['ptrace'][e['peer']][
                e['denied_mask']] = True
            return None

        elif e['operation'] == 'signal':
            self.hashlog[aamode][full_profile]['signal'][e['peer']][
                e['denied_mask']][e['signal']] = True
            return None

        elif e['operation'].startswith('dbus_'):
            self.hashlog[aamode][full_profile]['dbus'][e['denied_mask']][
                e['bus']][e['path']][e['name']][e['interface']][e['member']][
                    e['peer_profile']] = True
            return None

        else:
            self.debug_logger.debug('UNHANDLED: %s' % e)
Пример #8
0
    def __init__(self,
                 path,
                 perms,
                 exec_perms,
                 target,
                 owner,
                 file_keyword=False,
                 leading_perms=False,
                 audit=False,
                 deny=False,
                 allow_keyword=False,
                 comment='',
                 log_event=None):
        '''Initialize FileRule

           Parameters:
           - path: string, AARE or FileRule.ALL
           - perms: string, set of chars or FileRule.ALL (must not contain exec mode)
           - exec_perms: None or string
           - target: string, AARE or FileRule.ALL
           - owner: bool
           - file_keyword: bool
           - leading_perms: bool
        '''

        super(FileRule, self).__init__(audit=audit,
                                       deny=deny,
                                       allow_keyword=allow_keyword,
                                       comment=comment,
                                       log_event=log_event)

        #                                                               rulepart        partperms       is_path log_event
        self.path, self.all_paths = self._aare_or_all(path, 'path', True,
                                                      log_event)
        self.target, self.all_targets, = self._aare_or_all(
            target, 'target', False, log_event)

        self.can_glob = not self.all_paths
        self.can_glob_ext = not self.all_paths
        self.can_edit = not self.all_paths

        if type_is_str(perms):
            perms, tmp_exec_perms = split_perms(perms, deny)
            if tmp_exec_perms:
                raise AppArmorBug('perms must not contain exec perms')
        elif perms == None:
            perms = set()

        if perms == {'subset'}:
            raise AppArmorBug('subset without link permissions given')
        elif perms in [{'link'}, {'link', 'subset'}]:
            self.perms = perms
            self.all_perms = False
        else:
            self.perms, self.all_perms, unknown_items = check_and_split_list(
                perms,
                file_permissions,
                FileRule.ALL,
                'FileRule',
                'permissions',
                allow_empty_list=True)
            if unknown_items:
                raise AppArmorBug('Passed unknown perms to FileRule: %s' %
                                  str(unknown_items))
            if self.perms and 'a' in self.perms and 'w' in self.perms:
                raise AppArmorException(
                    "Conflicting permissions found: 'a' and 'w'")

        self.original_perms = None  # might be set by aa-logprof / aa.py propose_file_rules()

        if exec_perms is None:
            self.exec_perms = None
        elif 'link' in self.perms:
            raise AppArmorBug("link rules can't have execute permissions")
        elif exec_perms == self.ANY_EXEC:
            self.exec_perms = exec_perms
        elif type_is_str(exec_perms):
            if deny:
                if exec_perms != 'x':
                    raise AppArmorException(
                        _("file deny rules only allow to use 'x' as execute mode, but not %s"
                          % exec_perms))
            else:
                if exec_perms == 'x':
                    raise AppArmorException(
                        _("Execute flag ('x') in file rule must specify the exec mode (ix, Px, Cx etc.)"
                          ))
                elif exec_perms not in allow_exec_transitions and exec_perms not in allow_exec_fallback_transitions:
                    raise AppArmorBug(
                        'Unknown execute mode specified in file rule: %s' %
                        exec_perms)
            self.exec_perms = exec_perms
        else:
            raise AppArmorBug('Passed unknown perms object to FileRule: %s' %
                              str(perms))

        if type(owner) is not bool:
            raise AppArmorBug('non-boolean value passed to owner flag')
        self.owner = owner
        self.can_owner = owner  # offer '(O)wner permissions on/off' buttons only if the rule has the owner flag

        if type(file_keyword) is not bool:
            raise AppArmorBug('non-boolean value passed to file keyword flag')
        self.file_keyword = file_keyword

        if type(leading_perms) is not bool:
            raise AppArmorBug(
                'non-boolean value passed to leading permissions flag')
        self.leading_perms = leading_perms

        # XXX subset

        # check for invalid combinations (bare 'file,' vs. path rule)
        #       if (self.all_paths and not self.all_perms) or (not self.all_paths and self.all_perms):
        #           raise AppArmorBug('all_paths and all_perms must be equal')
        # elif
        if self.all_paths and (self.exec_perms or self.target):
            raise AppArmorBug(
                'exec perms or target specified for bare file rule')
Пример #9
0
    def edit_header(self):
        if self.all_paths:
            raise AppArmorBug('Attemp to edit bare file rule')

        return (_('Enter new path: '), self.path.regex)
Пример #10
0
    def is_covered_localvars(self, other_rule):
        '''check if other_rule is covered by this rule object'''

        if not self._is_covered_aare(self.path, self.all_paths,
                                     other_rule.path, other_rule.all_paths,
                                     'path'):
            return False

        if self.perms and 'subset' in self.perms and other_rule.perms and 'subset' not in other_rule.perms:
            return False  # subset is a restriction (also, if subset is included, this means this instance is a link rule, so other file permissions can't be covered)
        elif self.perms and 'link' in self.perms and other_rule.perms and 'link' in other_rule.perms:
            pass  # skip _is_covered_list() because it would interpret 'subset' as additional permissions, not as restriction
        elif not self._is_covered_list(perms_with_a(self.perms),
                                       self.all_perms,
                                       perms_with_a(other_rule.perms),
                                       other_rule.all_perms,
                                       'perms',
                                       sanity_check=False):
            # perms can be empty if only exec_perms are specified, therefore disable the sanity check in _is_covered_list()...
            # 'w' covers 'a', therefore use perms_with_a() to temporarily add 'a' if 'w' is present
            return False

        # TODO: check  link / link subset vs. 'l'?

        # ... and do our own sanity check
        if not other_rule.perms and not other_rule.all_perms and not other_rule.exec_perms:
            raise AppArmorBug(
                'No permission or exec permission specified in other file rule'
            )

        if not self.exec_perms and other_rule.exec_perms:
            return False

        # TODO: handle fallback modes?
        if other_rule.exec_perms == self.ANY_EXEC and self.exec_perms:
            pass  # other_rule has ANY_EXEC and self has an exec rule set -> covered, so avoid hitting the 'elif' branch
        elif other_rule.exec_perms and self.exec_perms != other_rule.exec_perms:
            return False

        # check exec_mode and target only if other_rule contains exec_perms (except ANY_EXEC) or link permissions
        # (for mrwk permissions, the target is ignored anyway)
        if (other_rule.exec_perms and other_rule.exec_perms != self.ANY_EXEC) or \
           (other_rule.perms and 'l' in other_rule.perms) or \
           (other_rule.perms and 'link' in other_rule.perms):
            if not self._is_covered_aare(self.target, self.all_targets,
                                         other_rule.target,
                                         other_rule.all_targets, 'target'):
                return False

            # a different target means running with a different profile, therefore we have to be more strict than _is_covered_aare()
            # XXX should we enforce an exact match for a) exec and/or b) link target?
            if self.all_targets != other_rule.all_targets:
                return False

        if self.owner and not other_rule.owner:
            return False

        # no check for file_keyword and leading_perms - they are not relevant for is_covered()

        # still here? -> then it is covered
        return True
Пример #11
0
    def store_edit(self, newpath):
        if self.all_paths:
            raise AppArmorBug('Attemp to edit bare file rule')

        self.path = AARE(newpath, True)  # might raise AppArmorException if the new path doesn't start with / or a variable
        self.raw_rule = None
Пример #12
0
    def validate_edit(self, newpath):
        if self.all_paths:
            raise AppArmorBug('Attemp to edit bare file rule')

        newpath = AARE(newpath, True)  # might raise AppArmorException if the new path doesn't start with / or a variable
        return newpath.match(self.path)
Пример #13
0
    def __init__(self,
                 execmode,
                 execcond,
                 targetprofile,
                 audit=False,
                 deny=False,
                 allow_keyword=False,
                 comment='',
                 log_event=None):
        '''
            CHANGE_PROFILE RULE = 'change_profile' [ [ EXEC MODE ] EXEC COND ] [ -> PROGRAMCHILD ]
        '''

        super(ChangeProfileRule, self).__init__(audit=audit,
                                                deny=deny,
                                                allow_keyword=allow_keyword,
                                                comment=comment,
                                                log_event=log_event)

        if execmode:
            if execmode != 'safe' and execmode != 'unsafe':
                raise AppArmorBug(
                    'Unknown exec mode (%s) in change_profile rule' % execmode)
            elif not execcond or execcond == ChangeProfileRule.ALL:
                raise AppArmorException(
                    'Exec condition is required when unsafe or safe keywords are present'
                )
        self.execmode = execmode

        self.execcond = None
        self.all_execconds = False
        if execcond == ChangeProfileRule.ALL:
            self.all_execconds = True
        elif type_is_str(execcond):
            if not execcond.strip():
                raise AppArmorBug(
                    'Empty exec condition in change_profile rule')
            elif execcond.startswith('/') or execcond.startswith('@'):
                self.execcond = execcond
            else:
                raise AppArmorException(
                    'Exec condition in change_profile rule does not start with /: %s'
                    % str(execcond))
        else:
            raise AppArmorBug(
                'Passed unknown object to ChangeProfileRule: %s' %
                str(execcond))

        self.targetprofile = None
        self.all_targetprofiles = False
        if targetprofile == ChangeProfileRule.ALL:
            self.all_targetprofiles = True
        elif type_is_str(targetprofile):
            if targetprofile.strip():
                self.targetprofile = targetprofile
            else:
                raise AppArmorBug(
                    'Empty target profile in change_profile rule')
        else:
            raise AppArmorBug(
                'Passed unknown object to ChangeProfileRule: %s' %
                str(targetprofile))
Пример #14
0
def re_match_include_parse(line, rule_name):
    '''Matches the path for include, include if exists and abi rules

    rule_name can be 'include' or 'abi'

    Returns a tuple with
    - if the "if exists" condition is given
    - the include/abi path
    - if the path is a magic path (enclosed in <...>)
    '''

    if rule_name == 'include':
        matches = RE_INCLUDE.search(line)
    elif rule_name == 'abi':
        matches = RE_ABI.search(line)
    else:
        raise AppArmorBug(
            're_match_include_parse() called with invalid rule name %s' %
            rule_name)

    if not matches:
        return None, None, None

    path = None
    ismagic = False
    if matches.group('magicpath'):
        path = matches.group('magicpath').strip()
        ismagic = True
    elif matches.group('unquotedpath'):
        path = matches.group('unquotedpath').strip()
        if re.search('\s', path):
            raise AppArmorException(
                _('Syntax error: %s must use quoted path or <...>') %
                rule_name)
        # LP: #1738879 - parser doesn't handle unquoted paths everywhere
        if rule_name == 'include':
            raise AppArmorException(
                _('Syntax error: %s must use quoted path or <...>') %
                rule_name)
    elif matches.group('quotedpath'):
        path = matches.group('quotedpath')
        # LP: 1738880 - parser doesn't handle relative paths everywhere, and
        # neither do we (see aa.py)
        if rule_name == 'include' and len(path) > 0 and path[0] != '/':
            raise AppArmorException(
                _('Syntax error: %s must use quoted path or <...>') %
                rule_name)

    # if path is empty or the empty string
    if path is None or path == "":
        raise AppArmorException(
            _('Syntax error: %s rule with empty filename') % rule_name)

    # LP: #1738877 - parser doesn't handle files with spaces in the name
    if rule_name == 'include' and re.search('\s', path):
        raise AppArmorException(
            _('Syntax error: %s rule filename cannot contain spaces') %
            rule_name)

    ifexists = False
    if rule_name == 'include' and matches.group('ifexists'):
        ifexists = True

    return path, ifexists, ismagic
Пример #15
0
 def get_glob(self, path_or_rule):
     '''Return the next possible glob. For rlimit rules, that can mean changing the value to 'infinity' '''
     # XXX implement all options mentioned above ;-)
     raise AppArmorBug('get_glob() is not (yet) available for this rule type')