diff --git a/utils/apparmor/aa.py b/utils/apparmor/aa.py index 97db8f015..6b8b79840 100644 --- a/utils/apparmor/aa.py +++ b/utils/apparmor/aa.py @@ -2616,6 +2616,7 @@ RE_PROFILE_HAT_DEF = re.compile('^\s*\^(\"??.+?\"??)\s+((flags=)?\((.+)\)\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*,)\s*(#.*)?$') +RE_PROFILE_MOUNT = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?((mount|remount|umount)[^#]*\s*,)\s*(#.*)?$') # match anything that's not " or #, or matching quotes with anything except quotes inside __re_no_or_quoted_hash = '([^#"]|"[^"]*")*' @@ -2693,6 +2694,7 @@ def parse_profile_data(data, file, do_include): profile_data[profile][hat]['allow']['netdomain'] = hasher() profile_data[profile][hat]['allow']['path'] = hasher() profile_data[profile][hat]['allow']['dbus'] = list() + profile_data[profile][hat]['allow']['mount'] = list() # Save the initial comment if initial_comment: profile_data[profile][hat]['initial_comment'] = initial_comment @@ -2966,6 +2968,28 @@ def parse_profile_data(data, file, do_include): dbus_rules.append(dbus_rule) profile_data[profile][hat][allow]['dbus'] = dbus_rules + elif RE_PROFILE_MOUNT.search(line): + matches = RE_PROFILE_MOUNT.search(line).groups() + + if not profile: + raise AppArmorException(_('Syntax Error: Unexpected mount 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' + mount = matches[2] + + mount_rule = parse_mount_rule(mount) + mount_rule.audit = audit + mount_rule.deny = (allow == 'deny') + + mount_rules = profile_data[profile][hat][allow].get('mount', list()) + mount_rules.append(mount_rule) + profile_data[profile][hat][allow]['mount'] = mount_rules + elif RE_PROFILE_CHANGE_HAT.search(line): matches = RE_PROFILE_CHANGE_HAT.search(line).groups() @@ -3060,6 +3084,10 @@ def parse_dbus_rule(line): # return aarules.DBUS_Rule() #print(line) +def parse_mount_rule(line): + # XXX Do real parsing here + return aarules.Raw_Mount_Rule(line) + def separate_vars(vs): """Returns a list of all the values for a variable""" data = [] @@ -3268,6 +3296,24 @@ def write_dbus(prof_data, depth): data += write_dbus_rules(prof_data, depth, 'allow') return data +def write_mount_rules(prof_data, depth, allow): + pre = ' ' * depth + data = [] + + # no mount rules, so return + if not prof_data[allow].get('mount', False): + return data + + for mount_rule in prof_data[allow]['mount']: + data.append('%s%s' % (pre, mount_rule.serialize())) + data.append('') + return data + +def write_mount(prof_data, depth): + data = write_mount_rules(prof_data, depth, 'deny') + data += write_mount_rules(prof_data, depth, 'allow') + return data + def write_link_rules(prof_data, depth, allow): pre = ' ' * depth data = [] @@ -3361,6 +3407,7 @@ def write_rules(prof_data, depth): data += write_capabilities(prof_data, depth) data += write_netdomain(prof_data, depth) data += write_dbus(prof_data, depth) + data += write_mount(prof_data, depth) data += write_links(prof_data, depth) data += write_paths(prof_data, depth) data += write_change_profile(prof_data, depth) @@ -3509,6 +3556,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): 'capability': write_capabilities, 'netdomain': write_netdomain, 'dbus': write_dbus, + 'mount': write_mount, 'link': write_links, 'path': write_paths, 'change_profile': write_change_profile, @@ -3600,6 +3648,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): 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_mount(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) diff --git a/utils/apparmor/rules.py b/utils/apparmor/rules.py index 9e14f417b..e34100f9b 100644 --- a/utils/apparmor/rules.py +++ b/utils/apparmor/rules.py @@ -55,3 +55,15 @@ class Raw_DBUS_Rule(object): return "%s%s%s" % ('audit ' if self.audit else '', 'deny ' if self.deny else '', self.rule) + +class Raw_Mount_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) diff --git a/utils/test/test-mount_parse.py b/utils/test/test-mount_parse.py new file mode 100644 index 000000000..12fa65c62 --- /dev/null +++ b/utils/test/test-mount_parse.py @@ -0,0 +1,70 @@ +#! /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 AAParseMountTest(unittest.TestCase): + + def test_parse_plain_mount_rule(self): + rule = 'mount,' + mount = aa.parse_mount_rule(rule) + self.assertEqual(rule, mount.serialize(), + 'mount object returned "%s", expected "%s"' % (mount.serialize(), rule)) + + def test_parse_ro_mount(self): + rule = 'mount -o ro,' + mount = aa.parse_mount_rule(rule) + self.assertEqual(rule, mount.serialize(), + 'mount object returned "%s", expected "%s"' % (mount.serialize(), rule)) + + def test_parse_rw_mount_with_mount_points(self): + rule = 'mount -o rw /dev/sdb1 -> /mnt/external,' + mount = aa.parse_mount_rule(rule) + self.assertEqual(rule, mount.serialize(), + 'mount object returned "%s", expected "%s"' % (mount.serialize(), rule)) + +class AAParseRemountTest(unittest.TestCase): + + def test_parse_plain_remount_rule(self): + rule = 'remount,' + mount = aa.parse_mount_rule(rule) + self.assertEqual(rule, mount.serialize(), + 'mount object returned "%s", expected "%s"' % (mount.serialize(), rule)) + + def test_parse_ro_remount(self): + rule = 'remount -o ro,' + mount = aa.parse_mount_rule(rule) + self.assertEqual(rule, mount.serialize(), + 'mount object returned "%s", expected "%s"' % (mount.serialize(), rule)) + + def test_parse_ro_remount_with_mount_point(self): + rule = 'remount -o ro /,' + mount = aa.parse_mount_rule(rule) + self.assertEqual(rule, mount.serialize(), + 'mount object returned "%s", expected "%s"' % (mount.serialize(), rule)) + +class AAParseUmountTest(unittest.TestCase): + + def test_parse_plain_umount_rule(self): + rule = 'umount,' + mount = aa.parse_mount_rule(rule) + self.assertEqual(rule, mount.serialize(), + 'mount object returned "%s", expected "%s"' % (mount.serialize(), rule)) + + def test_parse_umount_with_mount_point(self): + rule = 'umount /mnt/external,' + mount = aa.parse_mount_rule(rule) + self.assertEqual(rule, mount.serialize(), + 'mount object returned "%s", expected "%s"' % (mount.serialize(), rule)) + +if __name__ == '__main__': + unittest.main()