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