Hello,

Am Montag, 16. November 2015 schrieb Christian Boltz:
> Am Samstag, 24. Oktober 2015 schrieb Christian Boltz:
> > [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.
> 
> Here's v2 with the following changes:
> - handle log events properly (they contain raw strings, not regexes)
> - add convert_expression_to_aare()
> - rename self.regex_compiled to self._regex_compiled
> - add tests for convert_expression_to_aare(), AARE with log_event=True
> and __repr__()

After sending out v2, I noticed that equal() is a bad name and should be
is_equal() instead. Therefore here's v3 with the function renamed to is_equal():


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

=== modified file ./utils/apparmor/aare.py
--- utils/apparmor/aare.py      2015-11-16 21:42:15.073101290 +0100
+++ utils/apparmor/aare.py      2015-11-16 21:44:37.348324136 +0100
@@ -0,0 +1,83 @@
+# ----------------------------------------------------------------------
+#    Copyright (C) 2015 Christian Boltz <appar...@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, log_event=None):
+        '''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)
+
+        if log_event:
+            self.orig_regex = regex
+            self.regex = convert_expression_to_aare(regex)
+        else:
+            self.orig_regex = None
+            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:
+            if expression.orig_regex:
+                expression = expression.orig_regex
+            else:
+                return self.is_equal(expression)  # better safe than sorry
+        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 is_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.is_equal() called with unknown object: %s' 
% str(expression))
+
+
+def convert_expression_to_aare(expression):
+    '''convert an expression (taken from audit.log) to an AARE string'''
+
+    aare_escape_chars = ['\\', '?', '*', '[', ']', '{', '}', '"']
+    for char in aare_escape_chars:
+        expression = expression.replace(char, '\\' + char)
+
+    return expression
=== modified file ./utils/test/test-aare.py
--- utils/test/test-aare.py     2015-11-16 21:42:15.073101290 +0100
+++ utils/test/test-aare.py     2015-11-16 21:45:14.624121206 +0100
@@ -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, convert_expression_to_aare
 
 class TestConvert_regexp(AATest):
     tests = [
@@ -34,7 +35,24 @@
     def _run_test(self, params, expected):
         self.assertEqual(convert_regexp(params), expected)
 
-class TestExamplesConvert_regexp(AATest):
+class Test_convert_expression_to_aare(AATest):
+    tests = [
+        # note that \ always needs to be escaped in python, so \\ is actually 
just \ in the string
+        ('/foo',        '/foo'                  ),
+        ('/foo?',       '/foo\\?'               ),
+        ('/foo*',       '/foo\\*'               ),
+        ('/foo[bar]',   '/foo\\[bar\\]'         ),
+        ('/foo{bar}',   '/foo\\{bar\\}'         ),
+        ('/foo{',       '/foo\\{'               ),
+        ('/foo\\',      '/foo\\\\'              ),
+        ('/foo"',       '/foo\\"'               ),
+        ('}]"\\[{',     '\\}\\]\\"\\\\\\[\\{'   ),
+    ]
+
+    def _run_test(self, params, expected):
+        self.assertEqual(convert_expression_to_aare(params), expected)
+
+class TestConvert_regexpAndAAREMatch(AATest):
     tests = [
         #  aare                  path to check                         match 
expected?
         (['/foo/**/bar/',       '/foo/user/tools/bar/'              ], True),
@@ -117,6 +135,94 @@
         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.is_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.is_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 TestAAREMatchFromLog(AATest):
+    tests = [
+        #  AARE                 log event                  match expected?
+        (['/foo/bar',           '/foo/bar'              ], True),
+        (['/foo/*',             '/foo/bar'              ], True),
+        (['/**',                '/foo/bar'              ], True),
+        (['/foo/*',             '/bar/foo'              ], False),
+        (['/foo/*',             '/foo/"*'               ], True),
+        (['/foo/bar',           '/foo/*'                ], False),
+        (['/foo/?',             '/foo/('                ], True),
+        (['/foo/{bar,baz}',     '/foo/bar'              ], True),
+        (['/foo/{bar,baz}',     '/foo/bars'             ], False),
+    ]
+
+    def _run_test(self, params, expected):
+        regex, log_event = params
+        aare_obj_1 = AARE(regex, True)
+        aare_obj_2 = AARE(log_event, True, log_event=True)
+        self.assertEqual(aare_obj_1.match(aare_obj_2), expected)
+
+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.is_equal(check_for))
+        self.assertEqual(expected, aare_obj_1.is_equal(aare_obj_2))
+
+    def test_is_equal_invalid_1(self):
+        aare_obj = AARE('/foo/**', True)
+        with self.assertRaises(AppArmorBug):
+            aare_obj.is_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)
+
+class TestAARERepr(AATest):
+    def test_repr(self):
+        obj = AARE('/foo', True)
+        self.assertEqual(str(obj), "AARE('/foo')")
+
 
 setup_all_loops(__name__)
 if __name__ == '__main__':




Regards,

Christian Boltz
-- 
When you say "I wrote a program that crashed Windows", people just stare
at you blankly and say "Hey, I got those with the system, *for free*".
[Linus Torvalds]


-- 
AppArmor mailing list
AppArmor@lists.ubuntu.com
Modify settings or unsubscribe at: 
https://lists.ubuntu.com/mailman/listinfo/apparmor

Reply via email to