[apparmor] [patch] AARE class

Christian Boltz apparmor at cboltz.de
Sat Oct 24 14:40:45 UTC 2015


Hello,

Am Donnerstag, 22. Oktober 2015 schrieb Christian Boltz:
> Note: This is a proof-of-concept patch. I won't object if someone
> sends an ack, but the main goal of this mail is to get feedback if
> the way I've chosen looks sane or if I should change some things ;-)

In the meantime, the patch is out of the proof-of-concept phase :-)
so here's the official patch:

[patch] AARE class

The AARE class is meant to handle the internals of path AppArmor regexes
at various places / rule types (filename, signal peer etc.). The goal is
to use it in rule classes to hide all regex magic, so that the rule
class can just use the match() method.

The reason for delaying re.compile to match() is performance - I'd guess
a logprof run calls match() only for profiles with existing log events,
so we can save 90% of the re.compile() calls.

A possible optimization (on my TODO list) is to check if the given path is a
regex (contains {...,...}, [...], *, **, ?) or if we can do a plain string
comparison.

Handling/expanding variables is also on my TODO list.


The patch also includes several tests.


[ 15-aare-class-and-tests.diff ]

--- utils/apparmor/aare.py      2015-10-24 16:31:59.330982445 +0200
+++ utils/apparmor/aare.py      2015-10-24 16:22:11.368248916 +0200
@@ -0,0 +1,65 @@
+# ----------------------------------------------------------------------
+#    Copyright (C) 2015 Christian Boltz <apparmor at 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.
+#
+# ----------------------------------------------------------------------
+
+import re
+
+from apparmor.common import convert_regexp, AppArmorBug, AppArmorException
+
+class AARE(object):
+    '''AARE (AppArmor Regular Expression) wrapper class'''
+
+    def __init__(self, regex, is_path):
+        '''create an AARE instance for the given AppArmor regex
+        If is_path is true, the regex is expected to be a path and therefore must start with / or a variable.'''
+        # using the specified variables when matching.
+
+        if is_path:
+            if regex.startswith('/'):
+                pass
+            elif regex.startswith('@{'):
+                pass  # XXX ideally check variable content - each part must start with / - or another variable, which must start with /
+            else:
+                raise AppArmorException("Path doesn't start with / or variable: %s" % regex)
+
+        self.regex = regex
+
+        self.regex_compiled = None  # done on first use in match() - that saves us some re.compile() calls
+        # self.variables = variables  # XXX
+
+    def __repr__(self):
+        '''returns a "printable" representation of AARE'''
+        return "AARE('%s')" % self.regex
+
+    def match(self, expression):
+        '''check if the given expression (string or AARE) matches the regex'''
+
+        if type(expression) == AARE:
+            return self.equal(expression)
+        elif type(expression) != str:
+            raise AppArmorBug('AARE.match() called with unknown object: %s' % str(expression))
+
+        if self.regex_compiled is None:
+            self.regex_compiled = re.compile(convert_regexp(self.regex))
+
+        return bool(self.regex_compiled.match(expression))
+
+    def equal(self, expression):
+        '''check if the given expression is equal'''
+
+        if type(expression) == AARE:
+            return self.regex == expression.regex
+        elif type(expression) == str:
+            return self.regex == expression
+        else:
+            raise AppArmorBug('AARE.equal() called with unknown object: %s' % str(expression))
--- utils/test/test-aare.py     2015-10-24 16:31:59.330982445 +0200
+++ utils/test/test-aare.py     2015-10-24 14:30:13.886238501 +0200
@@ -14,7 +14,8 @@
 from common_test import AATest, setup_all_loops
 
 import re
-from apparmor.common import convert_regexp
+from apparmor.common import convert_regexp, AppArmorBug, AppArmorException
+from apparmor.aare import AARE
 
 class TestConvert_regexp(AATest):
     tests = [
@@ -34,7 +35,7 @@
     def _run_test(self, params, expected):
         self.assertEqual(convert_regexp(params), expected)
 
-class TestExamplesConvert_regexp(AATest):
+class TestConvert_regexpAndAAREMatch(AATest):
     tests = [
         #  aare                  path to check                         match expected?
         (['/foo/**/bar/',       '/foo/user/tools/bar/'              ], True),
@@ -117,6 +118,69 @@
         parsed_regex = re.compile(convert_regexp(regex))
         self.assertEqual(bool(parsed_regex.search(path)), expected, 'Incorrectly Parsed regex: %s' %regex)
 
+        aare_obj = AARE(regex, True)
+        self.assertEqual(aare_obj.match(path), expected, 'Incorrectly parsed AARE object: %s' % regex)
+
+    def test_multi_usage(self):
+        aare_obj = AARE('/foo/*', True)
+        self.assertTrue(aare_obj.match('/foo/bar'))
+        self.assertFalse(aare_obj.match('/foo/bar/'))
+        self.assertTrue(aare_obj.match('/foo/asdf'))
+
+    def test_match_against_AARE_1(self):
+        aare_obj_1 = AARE('@{foo}/[a-d]**', True)
+        aare_obj_2 = AARE('@{foo}/[a-d]**', True)
+        self.assertTrue(aare_obj_1.match(aare_obj_2))
+        self.assertTrue(aare_obj_1.equal(aare_obj_2))
+
+    def test_match_against_AARE_2(self):
+        aare_obj_1 = AARE('@{foo}/[a-d]**', True)
+        aare_obj_2 = AARE('@{foo}/*[a-d]*', True)
+        self.assertFalse(aare_obj_1.match(aare_obj_2))
+        self.assertFalse(aare_obj_1.equal(aare_obj_2))
+
+    def test_match_invalid_1(self):
+        aare_obj = AARE('@{foo}/[a-d]**', True)
+        with self.assertRaises(AppArmorBug):
+            aare_obj.match(set())
+
+class TestAAREIsEqual(AATest):
+    tests = [
+        # regex         is path?    check for       expected
+        (['/foo',       True,       '/foo'      ],  True ),
+        (['@{foo}',     True,       '@{foo}'    ],  True ),
+        (['/**',        True,       '/foo'      ],  False),
+    ]
+
+    def _run_test(self, params, expected):
+        regex, is_path, check_for = params
+        aare_obj_1 = AARE(regex, is_path)
+        aare_obj_2 = AARE(check_for, is_path)
+        self.assertEqual(expected, aare_obj_1.equal(check_for))
+        self.assertEqual(expected, aare_obj_1.equal(aare_obj_2))
+
+    def test_equal_invalid_1(self):
+        aare_obj = AARE('/foo/**', True)
+        with self.assertRaises(AppArmorBug):
+            aare_obj.equal(42)
+
+class TestAAREIsPath(AATest):
+    tests = [
+        # regex         is path?    match for       expected
+        (['/foo*',      True,       '/foobar'   ],  True ),
+        (['@{PROC}/',   True,       '/foobar'   ],  False),
+        (['foo*',       False,      'foobar'    ],  True ),
+    ]
+
+    def _run_test(self, params, expected):
+        regex, is_path, check_for = params
+        aare_obj = AARE(regex, is_path)
+        self.assertEqual(expected, aare_obj.match(check_for))
+
+    def test_path_missing_slash(self):
+        with self.assertRaises(AppArmorException):
+            AARE('foo*', True)
+
 
 setup_all_loops(__name__)
 if __name__ == '__main__':


Regards,

Christian Boltz
-- 
Der Vergleich hinkt auffallend. Wenn ein Auto so gebaut waere, wie
Microsoft seine Software vorkonfiguriert, wuerde das Bremspedal im
Handschuhfach liegen.                  [Stefan Savelsberg in dasr]




More information about the AppArmor mailing list