This patch adds very limited support for very dumb parsing of dbus rules. Basically, it stores dbus rules as raw strings wrapped in a class.
There's class structure to indicate how I'd like to see fuller future support for dbus rules to be implemented and act as a guidepost for how to handle most rules, moving away from the giant structure of nested dictionaries. A stub test script is included as well, with a modification to the make check target to set the PYTHONPATH to point in the right place. With this patch, aa-audit, aa-autodep, aa-complain, aa-disable, and aa-enforce all function for me. aa-logprof and aa-genprof have functionality issues for me at the moment (one of them dumps a backtrace even without this patch), and I'm not sure the writing out of dbus rules is completely implemented for modified profiles. Signed-off-by: Steve Beattie <[email protected]> --- utils/apparmor/aa.py | 64 ++++++++++++++++++++++++++++++++++++++++++ utils/apparmor/rules.py | 57 +++++++++++++++++++++++++++++++++++++ utils/test/Makefile | 2 - utils/test/test-dbus_parse.py | 30 +++++++++++++++++++ 4 files changed, 152 insertions(+), 1 deletion(-) Index: b/utils/apparmor/aa.py =================================================================== --- a/utils/apparmor/aa.py +++ b/utils/apparmor/aa.py @@ -39,6 +39,8 @@ from apparmor.aamode import (str_to_mode mode_to_str_user, mode_contains, AA_OTHER, flatten_mode, owner_flatten_mode) +import apparmor.rules as aarules + from apparmor.yasti import SendDataToYast, GetDataFromYast, shutdown_yast # setup module translations @@ -2613,6 +2615,7 @@ RE_PROFILE_CHANGE_HAT = re.compile('^\s* RE_PROFILE_HAT_DEF = re.compile('^\s*\^(\"??.+?\"??)\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$') RE_NETWORK_FAMILY_TYPE = re.compile('\s+(\S+)\s+(\S+)\s*,$') RE_NETWORK_FAMILY = re.compile('\s+(\S+)\s*,$') +RE_PROFILE_DBUS = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?(dbus[^#]*)\s*(#.*)?$') def parse_profile_data(data, file, do_include): profile_data = hasher() @@ -2676,6 +2679,7 @@ def parse_profile_data(data, file, do_in profile_data[profile][hat]['allow']['netdomain'] = hasher() profile_data[profile][hat]['allow']['path'] = hasher() + profile_data[profile][hat]['allow']['dbus'] = list() # Save the initial comment if initial_comment: profile_data[profile][hat]['initial_comment'] = initial_comment @@ -2926,6 +2930,29 @@ def parse_profile_data(data, file, do_in profile_data[profile][hat][allow]['netdomain']['rule']['all'] = True profile_data[profile][hat][allow]['netdomain']['audit']['all'] = audit # True + elif RE_PROFILE_DBUS.search(line): + matches = RE_PROFILE_DBUS.search(line).groups() + + if not profile: + raise AppArmorException(_('Syntax Error: Unexpected dbus entry found in file: %s line: %s') % (file, lineno + 1)) + + audit = False + if matches[0]: + audit = True + allow = 'allow' + if matches[1] and matches[1].strip() == 'deny': + allow = 'deny' + dbus = matches[2] + + #parse_dbus_rule(profile_data[profile], dbus, audit, allow) + dbus_rule = parse_dbus_rule(dbus) + dbus_rule.audit = audit + dbus_rule.deny = (allow == 'deny') + + dbus_rules = profile_data[profile][hat][allow].get('dbus', list()) + dbus_rules.append(dbus_rule) + profile_data[profile][hat][allow]['dbus'] = dbus_rules + elif RE_PROFILE_CHANGE_HAT.search(line): matches = RE_PROFILE_CHANGE_HAT.search(line).groups() @@ -2998,6 +3025,21 @@ def parse_profile_data(data, file, do_in return profile_data +# RE_DBUS_ENTRY = re.compile('^dbus\s*()?,\s*$') +# use stuff like '(?P<action>(send|write|w|receive|read|r|rw))' + +def parse_dbus_rule(line): + # XXX Do real parsing here + return aarules.Raw_DBUS_Rule(line) + + #matches = RE_DBUS_ENTRY.search(line).groups() + #if len(matches) == 1: + # XXX warn? + # matched nothing + # print('no matches') + # return aarules.DBUS_Rule() + #print(line) + def separate_vars(vs): """Returns a list of all the values for a variable""" data = [] @@ -3188,6 +3230,24 @@ def write_netdomain(prof_data, depth): data += write_net_rules(prof_data, depth, 'allow') return data +def write_dbus_rules(prof_data, depth, allow): + pre = ' ' * depth + data = [] + + # no dbus rules, so return + if not prof_data[allow].get('dbus', False): + return data + + for dbus_rule in prof_data[allow]['dbus']: + data.append('%s%s' % (pre, dbus_rule.serialize())) + data.append('') + return data + +def write_dbus(prof_data, depth): + data = write_dbus_rules(prof_data, depth, 'deny') + data += write_net_rules(prof_data, depth, 'allow') + return data + def write_link_rules(prof_data, depth, allow): pre = ' ' * depth data = [] @@ -3280,6 +3340,7 @@ def write_rules(prof_data, depth): data += write_rlimits(prof_data, depth) data += write_capabilities(prof_data, depth) data += write_netdomain(prof_data, depth) + data += write_dbus(prof_data, depth) data += write_links(prof_data, depth) data += write_paths(prof_data, depth) data += write_change_profile(prof_data, depth) @@ -3427,6 +3488,7 @@ def serialize_profile_from_old_profile(p 'rlimit': write_rlimits, 'capability': write_capabilities, 'netdomain': write_netdomain, + 'dbus': write_dbus, 'link': write_links, 'path': write_paths, 'change_profile': write_change_profile, @@ -3438,6 +3500,7 @@ def serialize_profile_from_old_profile(p 'rlimit': False, 'capability': False, 'netdomain': False, + 'dbus': False, 'link': False, 'path': False, 'change_profile': False, @@ -3516,6 +3579,7 @@ def serialize_profile_from_old_profile(p data += write_rlimits(write_prof_data, depth) data += write_capabilities(write_prof_data[name], depth) data += write_netdomain(write_prof_data[name], depth) + data += write_dbus(write_prof_data[name], depth) data += write_links(write_prof_data[name], depth) data += write_paths(write_prof_data[name], depth) data += write_change_profile(write_prof_data[name], depth) Index: b/utils/apparmor/rules.py =================================================================== --- /dev/null +++ b/utils/apparmor/rules.py @@ -0,0 +1,57 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2014 Canonical Ltd. +# +# 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 published by the Free Software Foundation. +# +# ------------------------------------------------------------------ + +class DBUS_Rule(object): + actions = set() + busses = set() + names = set() + paths = set() + interfaces = set() + members = set() + peer_names = set() + peer_labels = set() + + audit = False + deny = False + + def __init__(self, actions=[], busses=[], names=[], paths=[], interfaces=[], + members=[], peer_names=[], peer_labels=[]): + self.actions = set(actions) + self.busses = set(busses) + self.names = set(names) + self.paths = set(paths) + self.interfaces = set(interfaces) + self.members = set(members) + self.peer_name = set(peer_names) + self.peer_labels = set(peer_labels) + + def serialize(self): + out = "%s%s%s" % ('audit ' if self.audit else '', + 'deny ' if self.deny else '', + 'dbus') + if len(self.actions) > 0: + if len(self.actions) == 1: + out += ' %s' % self.actions[0] + else: + out += ' (%s)' % (', '.join(self.actions)) + out += ',' + return out + +class Raw_DBUS_Rule(object): + audit = False + deny = False + + def __init__(self, rule): + self.rule = rule + + def serialize(self): + return "%s%s%s" % ('audit ' if self.audit else '', + 'deny ' if self.deny else '', + self.rule) Index: b/utils/test/test-dbus_parse.py =================================================================== --- /dev/null +++ b/utils/test/test-dbus_parse.py @@ -0,0 +1,30 @@ +#! /usr/bin/env python +# ------------------------------------------------------------------ +# +# Copyright (C) 2014 Canonical Ltd. +# +# 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 published by the Free Software Foundation. +# +# ------------------------------------------------------------------ + +import apparmor.aa as aa +import unittest + +class AAParseDBUSTest(unittest.TestCase): + + def test_parse_plain_dbus_rule(self): + dstring = 'dbus,' + dbus = aa.parse_dbus_rule(dstring) + self.assertEqual(dstring, dbus.serialize(), + 'dbus object returned "%s", expected "%s"' % (dbus.serialize(), dstring)) + + def test_parse_dbus_simple_send_rule(self): + dstring = 'dbus send,' + dbus = aa.parse_dbus_rule(dstring) + self.assertEqual(dstring, dbus.serialize(), + 'dbus object returned "%s", expected "%s"' % (dbus.serialize(), dstring)) + +if __name__ == '__main__': + unittest.main() Index: b/utils/test/Makefile =================================================================== --- a/utils/test/Makefile +++ b/utils/test/Makefile @@ -38,4 +38,4 @@ ifndef VERBOSE .SILENT: check endif check: - $(foreach test, $(wildcard test-*.py), $(call pyalldo, $(test))) + export PYTHONPATH=.. ; $(foreach test, $(wildcard test-*.py), $(call pyalldo, $(test))) -- AppArmor mailing list [email protected] Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/apparmor
