[apparmor] [PATCH] utils: Add very limited support for mount rules

Tyler Hicks tyhicks at canonical.com
Thu Mar 20 00:26:54 UTC 2014


Bug: https://bugs.launchpad.net/bugs/1294825

This patch is inspired by sbeattie's patch to add limited dbus rule
support. It adds does very dumb parsing of mount rules. Basically, it
stores mount, remount, and umount rules as raw strings wrapped in a
class.

Signed-off-by: Tyler Hicks <tyhicks at canonical.com>
---
 utils/apparmor/aa.py           | 49 +++++++++++++++++++++++++++++
 utils/apparmor/rules.py        | 12 ++++++++
 utils/test/test-mount_parse.py | 70 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 131 insertions(+)
 create mode 100644 utils/test/test-mount_parse.py

diff --git a/utils/apparmor/aa.py b/utils/apparmor/aa.py
index ddbee52..8dd35a3 100644
--- a/utils/apparmor/aa.py
+++ b/utils/apparmor/aa.py
@@ -2619,6 +2619,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 = '([^#"]|"[^"]*")*'
@@ -2696,6 +2697,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
@@ -2971,6 +2973,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()
 
@@ -3065,6 +3089,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 = []
@@ -3276,6 +3304,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 = []
@@ -3369,6 +3415,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)
@@ -3517,6 +3564,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,
@@ -3608,6 +3656,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 9e14f41..e34100f 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 0000000..12fa65c
--- /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()
-- 
1.9.0




More information about the AppArmor mailing list