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 <kgupta8...@gmail.com> +# Copyright (C) 2015 Christian Boltz <appar...@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)) + else: + raise AppArmorBug('Empty exec condition in change_profile rule') + 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(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@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/apparmor