Hello,
Am Samstag, 22. November 2014 schrieb Christian Boltz:
> The updated patch (v2) is attached.
And here's v3 ;-)
Changes since v2:
- CapabilityRuleset: add add_obj() function
- CapabilityRule: add set_param() function
Regards,
Christian Boltz
--
"DOS=HIGH ...I knew it was on something!"
(UNIX user, while reading C:\CONFIG.SYS)
=== added directory 'utils/apparmor/rule'
=== added file 'utils/apparmor/rule/__init__.py'
--- utils/apparmor/rule/__init__.py 1970-01-01 00:00:00 +0000
+++ utils/apparmor/rule/__init__.py 2014-11-22 22:49:45 +0000
@@ -0,0 +1,249 @@
+# ----------------------------------------------------------------------
+# Copyright (C) 2013 Kshitij Gupta <[email protected]>
+# Copyright (C) 2014 Christian Boltz <[email protected]>
+#
+# 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.common import AppArmorBug
+
+# setup module translations
+from apparmor.translations import init_translation
+_ = init_translation()
+
+
+class BaseRule(object):
+ '''Base class to handle and store a single rule'''
+
+ def __init__(self):
+ '''initialize variables needed by all rule types
+ Do not override in child class unless really needed - override init_vars() instead'''
+ self.audit = False
+ self.deny = False
+ self.allow_keyword = False
+
+ self.inlinecomment = ''
+
+ self.rawrule = ''
+ self.logmode = '' # only set by set_log()
+
+ self.init_vars()
+
+ def init_vars(self):
+ '''called by __init__() - override in child class to initialize more variables'''
+ raise AppArmorBug('init_vars() needs to be implemented in the child class')
+
+ def get_raw(self, depth):
+ '''return raw rule (with original formatting, and leading whitespace in the depth parameter)'''
+ if self.rawrule:
+ return '%s%s' % (' ' * depth, self.rawrule)
+ else:
+ return self.get_clean(depth)
+
+ def is_equal(self, rule_obj):
+ '''compare if rule_obj == self
+ Calls is_equal_localvars() to compare rule-specific variables'''
+
+ if (self.audit != rule_obj.audit
+ or self.deny != rule_obj.deny
+ # XXX how strict should the comparison be?
+ # or self.allow_keyword != rule_obj.allow_keyword
+ # or self.inlinecomment != rule_obj.inlinecomment
+ # or self.rawrule != rule_obj.rawrule
+ ):
+ return False
+
+ return self.is_equal_localvars(rule_obj)
+
+ def modifiers_str(self):
+ '''return the allow/deny and audit keyword as string, including whitespace'''
+
+ if self.audit:
+ auditstr = 'audit '
+ else:
+ auditstr = ''
+
+ if self.deny:
+ allowstr = 'deny '
+ elif self.allow_keyword:
+ allowstr = 'allow '
+ else:
+ allowstr = ''
+
+ return '%s%s' % (auditstr, allowstr)
+
+ def parse_audit_allow(self, 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 = ''
+ if matches.group('comment'):
+ # include a space so that we don't need to add it everywhere when writing the rule
+ comment = ' %s' % matches.group('comment')
+
+ return (audit, deny, allow_keyword, comment)
+
+
+class BaseRuleset(object):
+ '''Base class to handle and store a collection of rules'''
+
+ # decides if the (G)lob and Glob w/ (E)xt options are displayed
+ can_glob = True
+ can_glob_ext = False
+
+ def __init__(self):
+ '''initialize variables needed by all ruleset types
+ Do not override in child class unless really needed - override init_vars() instead'''
+ self.delete_all_rules()
+
+ def delete_all_rules(self):
+ self.rules = []
+ self.init_vars()
+
+ def init_vars(self):
+ '''called by __init__() and delete_all_rules() - override in child class to initialize more variables'''
+ pass
+
+ def add_raw(self, rawrule):
+ '''parse rawrule (from profile file) and store it in a structured way'''
+
+ newrule = self.new_rule()
+ newrule.set_raw(rawrule)
+ self.rules.append(newrule)
+
+ def add_obj(self, rule_obj):
+ '''add a rule object'''
+ self.rules.append(rule_obj)
+
+ def get_raw(self, depth):
+ '''return all raw rules (if possible/not modified in their original formatting).
+ Returns an array of lines, with depth * leading whitespace'''
+
+ data = []
+ for rule in self.rules:
+ data.append(rule.get_raw(depth))
+
+ if data:
+ data.append('')
+
+ return data
+
+ def get_clean(self, depth):
+ '''return all rules (in clean/default formatting)
+ Returns an array of lines, with depth * leading whitespace'''
+
+ allow_rules = []
+ deny_rules = []
+
+ for rule in self.rules:
+ if rule.deny:
+ deny_rules.append(rule.get_clean(depth))
+ else:
+ allow_rules.append(rule.get_clean(depth))
+
+ allow_rules.sort()
+ deny_rules.sort()
+
+ cleandata = []
+
+ if deny_rules:
+ cleandata += deny_rules
+ cleandata.append('')
+
+ if allow_rules:
+ cleandata += allow_rules
+ cleandata.append('')
+
+ return cleandata
+
+ def is_obj_covered(self, rule_obj, check_allow_deny=True, check_audit=False):
+ '''return True if rule_obj is covered by existing rules, otherwise False'''
+
+ for rule in self.rules:
+ if rule.is_covered(rule_obj, check_allow_deny, check_audit):
+ return True
+
+ return False
+
+ def is_log_covered(self, parsed_log_event, check_allow_deny=True, check_audit=False):
+ '''return True if parsed_log_event is covered by existing rules, otherwise False'''
+
+ rule_obj = self.new_rule()
+ rule_obj.set_log(parsed_log_event)
+
+ return self.is_obj_covered(rule_obj, check_allow_deny, check_audit)
+
+ def is_raw_covered(self, rawrule, check_allow_deny=True, check_audit=False):
+ '''return True if rawrule is covered by existing rules, otherwise False'''
+
+ rule_obj = self.new_rule()
+ rule_obj.set_raw(rawrule)
+
+ return self.is_obj_covered(rule_obj, check_allow_deny, check_audit)
+
+ def delete_obj(self, rule_obj):
+ '''Delete rule_obj from rules'''
+
+ rule_to_delete = None
+ i = 0
+ for rule in self.rules:
+ i = i + 1
+ if rule.is_equal(rule_obj):
+ rule_to_delete = i
+ break
+
+ if rule_to_delete:
+ self.rules.pop(i-1)
+ else:
+ raise AppArmorBug('Attemp to delete non-existing rule %s' % rule_obj.get_raw(0))
+
+ def delete_raw(self, rawrule):
+ '''Delete rawrule from rules'''
+
+ rule_obj = self.new_rule()
+ rule_obj.set_raw(rawrule)
+
+ return self.delete_obj(rule_obj)
+
+ def delete_duplicates(self, include_rules):
+ '''Delete duplicate rules.
+ include_rules must be a *_rules object'''
+ deleted = []
+ if include_rules: # avoid breakage until we have a proper function to ensure all profiles contain all *_rules objects
+ for rule_obj in self.rules:
+ if include_rules.is_obj_covered(rule_obj, True, True):
+ self.delete_obj(rule_obj)
+ deleted.append(rule_obj)
+
+ return len(deleted)
+
+ def get_glob_ext(self, path_or_rule):
+ '''returns the next possible glob with extension (for file rules only).
+ For all other rule types, raise an exception'''
+ raise AppArmorBug("get_glob_ext is not available for this rule type!")
+
+
=== added file 'utils/apparmor/rule/capability.py'
--- utils/apparmor/rule/capability.py 1970-01-01 00:00:00 +0000
+++ utils/apparmor/rule/capability.py 2014-11-22 23:31:35 +0000
@@ -0,0 +1,125 @@
+# ----------------------------------------------------------------------
+# Copyright (C) 2013 Kshitij Gupta <[email protected]>
+# Copyright (C) 2014 Christian Boltz <[email protected]>
+#
+# 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_CAP
+from apparmor.common import AppArmorBug, AppArmorException
+from apparmor.rule import BaseRule, BaseRuleset
+import re
+
+# setup module translations
+from apparmor.translations import init_translation
+_ = init_translation()
+
+
+class CapabilityRule(BaseRule):
+ '''Class to handle and store a single capability rule'''
+
+ def init_vars(self):
+ self.capability = [] # XXX use {} instead to enforce in-rule duplicates ("capability chown chown,") are filtered?
+ self.all_caps = False
+
+ def get_clean(self, depth):
+ '''return rule (in clean/default formatting)'''
+
+ space = ' ' * depth
+ if self.all_caps:
+ return('%s%scapability,%s' % (space, self.modifiers_str(), self.inlinecomment))
+ else:
+ caps = ' '.join(self.capability).strip() # XXX return multiple lines, one for each capability, instead?
+ if caps:
+ return('%s%scapability %s,%s' % (space, self.modifiers_str(), ' '.join(sorted(self.capability)), self.inlinecomment))
+ else:
+ raise AppArmorBug("Empty capability rule")
+
+ def set_raw(self, rawrule):
+ '''parse and store rawrule'''
+
+ matches = RE_PROFILE_CAP.search(rawrule)
+ if not matches:
+ raise AppArmorException(_("Invalid capability rule '%s'") % rawrule)
+
+ self.rawrule = rawrule.strip()
+
+ self.audit, self.deny, self.allow_keyword, self.inlinecomment = self.parse_audit_allow(matches)
+
+ if matches.group('capability'):
+ capability = matches.group('capability').strip()
+ self.capability = re.split("[ \t]+", capability)
+ else:
+ self.all_caps = True
+
+ def set_log(self, parsed_log_event):
+ '''parse and store log event'''
+
+ if parsed_log_event['operation'] != 'capable':
+ raise AppArmorBug('Invalid capability log event - not a capability event')
+
+ if not parsed_log_event['name']:
+ raise AppArmorBug('Invalid capability log event - name is empty')
+
+ self.capability = [ parsed_log_event['name'] ]
+
+ self.logmode = parsed_log_event['aamode']
+
+ def set_param(self, capability, audit=False, deny=False):
+ self.capability = [ capability ]
+ self.audit = audit
+ self.deny = deny
+
+ def is_covered(self, rule_obj, check_allow_deny=True, check_audit=False):
+ '''check if rule_obj is covered by this rule object'''
+
+ if check_allow_deny and self.deny != rule_obj.deny:
+ return False
+
+ if not rule_obj.capability and not rule_obj.all_caps:
+ raise AppArmorBug('No capability specified')
+
+ if not self.all_caps:
+ if rule_obj.all_caps:
+ return False
+ for cap in rule_obj.capability:
+ if not cap in self.capability:
+ return False
+
+ if check_audit and rule_obj.audit != self.audit:
+ return False
+
+ if rule_obj.audit and not self.audit:
+ 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 ( self.capability != rule_obj.capability
+ or self.all_caps != rule_obj.all_caps ):
+ return False
+
+ return True
+
+class CapabilityRuleset(BaseRuleset):
+ '''Class to handle and store a collection of capability rules'''
+
+ def new_rule(self):
+ '''tiny helper function that allows to keep several functions to parent class'''
+ return CapabilityRule()
+
+ def get_glob(self, path_or_rule):
+ '''Return the next possible glob. For capability rules, that's always "capability," (all capabilities)'''
+ return 'capability,'
+
--
AppArmor mailing list
[email protected]
Modify settings or unsubscribe at:
https://lists.ubuntu.com/mailman/listinfo/apparmor