[apparmor] [1/4] Add ChangeProfileRule and ChangeProfileRuleset classes

Steve Beattie steve at nxnw.org
Thu May 28 02:35:35 UTC 2015


On Sat, May 09, 2015 at 10:35:54PM +0200, Christian Boltz wrote:
> Hello,
> 
> this patch adds utils/apparmor/rule/change_profile.py with the 
> ChangeProfileRule and ChangeProfileRuleset classes. These classes are 
> meant to handle change_profile rules.
> 
> In comparison to the current code in aa.py, ChangeProfileRule has some
> added features:
> - support for audit and allow/deny keywords (for which John promised a
>   parser patch really soon)
> - support for change_profile rules with an exec condition
> 
> Also add the improved regex RE_PROFILE_CHANGE_PROFILE_2 to regex.py.
> 
> 
> [ 01-add-ChangeProfileRule.diff ]
> 
> === modified file utils/apparmor/regex.py
> --- utils/apparmor/regex.py     2015-05-08 21:27:52.890348306 +0200
> +++ utils/apparmor/regex.py     2015-05-09 22:00:07.611093982 +0200
> @@ -72,6 +72,16 @@
>      '\s+((flags=)?\((?P<flags>.+)\)\s+)?\{' +
>      RE_EOL)
>  
> +
> +RE_PROFILE_CHANGE_PROFILE_2 = re.compile(
> +    RE_AUDIT_DENY +
> +    'change_profile' +
> +    '(\s+' + RE_PROFILE_PATH % 'execcond' + ')?' +  # optionally exec condition
> +    '(\s+->\s*' + RE_PROFILE_NAME % 'targetprofile' + ')?' +  # optionally '->' target profile
> +    RE_COMMA_EOL)
> +
> +
> +
>  def parse_profile_start_line(line, filename):
>      matches = RE_PROFILE_START.search(line)
>  
> === modified file utils/apparmor/rule/change_profile.py
> --- utils/apparmor/rule/change_profile.py       2015-05-09 22:05:27.191505750 +0200
> +++ utils/apparmor/rule/change_profile.py       2015-05-09 20:25:33.333309986 +0200
> @@ -0,0 +1,173 @@
> +#!/usr/bin/env python
> +# ----------------------------------------------------------------------
> +#    Copyright (C) 2013 Kshitij Gupta <kgupta8592 at gmail.com>
> +#    Copyright (C) 2015 Christian Boltz <apparmor at cboltz.de>
> +#
> +#    This program is free software; you can redistribute it and/or
> +#    modify it under the terms of version 2 of the GNU General Public
> +#    License as published by the Free Software Foundation.
> +#
> +#    This program is distributed in the hope that it will be useful,
> +#    but WITHOUT ANY WARRANTY; without even the implied warranty of
> +#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +#    GNU General Public License for more details.
> +#
> +# ----------------------------------------------------------------------
> +
> +from apparmor.regex import RE_PROFILE_CHANGE_PROFILE_2, strip_quotes
> +from apparmor.common import AppArmorBug, AppArmorException
> +from apparmor.rule import BaseRule, BaseRuleset, parse_modifiers, quote_if_needed
> +
> +# setup module translations
> +from apparmor.translations import init_translation
> +_ = init_translation()
> +
> +
> +class ChangeProfileRule(BaseRule):
> +    '''Class to handle and store a single network rule'''
> +
> +    # Nothing external should reference this class, all external users
> +    # should reference the class field ChangeProfileRule.ALL
> +    class __ChangeProfileAll(object):
> +        pass
> +
> +    ALL = __ChangeProfileAll
> +
> +    def __init__(self, execcond, targetprofile, audit=False, deny=False, allow_keyword=False,
> +                 comment='', log_event=None):
> +
> +        '''
> +            CHANGE_PROFILE RULE = 'change_profile' [ EXEC COND ] [ -> PROGRAMCHILD ]
> +        '''
> +
> +        super(ChangeProfileRule, self).__init__(audit=audit, deny=deny,
> +                                             allow_keyword=allow_keyword,
> +                                             comment=comment,
> +                                             log_event=log_event)
> +
> +        self.execcond = None
> +        self.all_execconds = False
> +        if execcond == ChangeProfileRule.ALL:
> +            self.all_execconds = True
> +        elif type(execcond) == str:
> +            if execcond.strip():
> +                if execcond.startswith('/'):
> +                    self.execcond = execcond
> +                else:
> +                    raise AppArmorException('Exec condition in change_profile rule does not start with /: %s' % str(execcond))

This is problematic in the situation where paths begin with an @{}
variable (e.g. 'change_profile @{MY_PROGRAM} -> my_program_profile,').
Granted, the parser doesn't currently accept this syntax, so...

> +            else:
> +                raise AppArmorBug('Empty exec condition in change_profile rule')
> +        else:
> +            raise AppArmorBug('Passed unknown object to ChangeProfileRule: %s' % str(execcond))

That said, I also agree with Seth's comment, it might be nice to
restructure the if else clauses like so:

        elif type(execcond) == str:
            if not execcond.strip():
                raise AppArmorBug('Empty exec condition in change_profile rule')
            if not execcond.is_valid_path():  # or whatever the test is here
                raise AppArmorException('Exec condition in change_profile rule does not start with /: %s' % str(execcond))
            self.execcond = execcond
        else:
            raise AppArmorBug('Passed unknown object to ChangeProfileRule: %s' % str(execcond))

Otherwise, this is looking okay.

> +        self.targetprofile = None
> +        self.all_targetprofiles = False
> +        if targetprofile == ChangeProfileRule.ALL:
> +            self.all_targetprofiles = True
> +        elif type(targetprofile) == str:
> +            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))
> +
> +    @classmethod
> +    def _match(cls, raw_rule):
> +        return RE_PROFILE_CHANGE_PROFILE_2.search(raw_rule)
> +
> +    @classmethod
> +    def _parse(cls, raw_rule):
> +        '''parse raw_rule and return ChangeProfileRule'''
> +
> +        matches = cls._match(raw_rule)
> +        if not matches:
> +            raise AppArmorException(_("Invalid change_profile rule '%s'") % raw_rule)
> +
> +        audit, deny, allow_keyword, comment = parse_modifiers(matches)
> +
> +        if matches.group('execcond'):
> +            execcond = strip_quotes(matches.group('execcond'))
> +        else:
> +            execcond = ChangeProfileRule.ALL
> +
> +        if matches.group('targetprofile'):
> +            targetprofile = strip_quotes(matches.group('targetprofile'))
> +        else:
> +            targetprofile = ChangeProfileRule.ALL
> +
> +        return ChangeProfileRule(execcond, targetprofile,
> +                           audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment)
> +
> +    def get_clean(self, depth=0):
> +        '''return rule (in clean/default formatting)'''
> +
> +        space = '  ' * depth
> +
> +        if self.all_execconds:
> +            execcond = ''
> +        elif self.execcond:
> +            execcond = ' %s' % quote_if_needed(self.execcond)
> +        else:
> +            raise AppArmorBug('Empty execcond in change_profile rule')
> +
> +        if self.all_targetprofiles:
> +            targetprofile = ''
> +        elif self.targetprofile:
> +            targetprofile = ' -> %s' % quote_if_needed(self.targetprofile)
> +        else:
> +            raise AppArmorBug('Empty target profile in change_profile rule')
> +
> +        return('%s%schange_profile%s%s,%s' % (space, self.modifiers_str(), execcond, targetprofile, self.comment))
> +
> +    def is_covered_localvars(self, other_rule):
> +        '''check if other_rule is covered by this rule object'''
> +
> +        if not other_rule.execcond and not other_rule.all_execconds:
> +            raise AppArmorBug('No execcond specified in other change_profile rule')
> +
> +        if not other_rule.targetprofile and not other_rule.all_targetprofiles:
> +            raise AppArmorBug('No target profile specified in other change_profile rule')
> +
> +        if not self.all_execconds:
> +            if other_rule.all_execconds:
> +                return False
> +            if other_rule.execcond != self.execcond:
> +                # TODO: honor globbing and variables
> +                return False
> +
> +        if not self.all_targetprofiles:
> +            if other_rule.all_targetprofiles:
> +                return False
> +            if other_rule.targetprofile != self.targetprofile:
> +                return False
> +
> +        # still here? -> then it is covered
> +        return True
> +
> +    def is_equal_localvars(self, rule_obj):
> +        '''compare if rule-specific variables are equal'''
> +
> +        if not type(rule_obj) == ChangeProfileRule:
> +            raise AppArmorBug('Passed non-change_profile rule: %s' % str(rule_obj))
> +
> +        if (self.execcond != rule_obj.execcond
> +                or self.all_execconds != rule_obj.all_execconds):
> +            return False
> +
> +        if (self.targetprofile != rule_obj.targetprofile
> +                or self.all_targetprofiles != rule_obj.all_targetprofiles):
> +            return False
> +
> +        return True
> +
> +
> +class ChangeProfileRuleset(BaseRuleset):
> +    '''Class to handle and store a collection of change_profile rules'''
> +
> +    def get_glob(self, path_or_rule):
> +        '''Return the next possible glob. For change_profile rules, that can be "change_profile EXECCOND,",
> +           "change_profile -> TARGET_PROFILE," or "change_profile," (all change_profile).
> +           Also, EXECCOND filename can be globbed'''
> +        # XXX implement all options mentioned above ;-)
> +        return 'change_profile,'
> 
> 
> 
> Regards,
> 
> Christian Boltz
> -- 
> Graphisch??? Wie meinen? Hast du zuviel Fleisch von zu "gluecklichen"
> Rindern gefuttert? *scnr*  Wozu zum Henker sollte man sowas brauchen?
> Logo ginge auch per ASCII :)  (Logo?  welches Logo? Wozu ueberhaupt?)
> [David Haller in suse-linux]
> 
> 
> -- 
> AppArmor mailing list
> AppArmor at lists.ubuntu.com
> Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/apparmor

-- 
Steve Beattie
<sbeattie at ubuntu.com>
http://NxNW.org/~steve/
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <https://lists.ubuntu.com/archives/apparmor/attachments/20150527/47626045/attachment.pgp>


More information about the AppArmor mailing list