[apparmor] [patch] AARE class
Christian Boltz
apparmor at cboltz.de
Mon Nov 16 20:24:01 UTC 2015
Hello,
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__()
[ 15-aare-class-and-tests.diff ]
=== modified file ./utils/apparmor/aare.py
--- utils/apparmor/aare.py 2015-11-16 21:14:18.839452792 +0100
+++ utils/apparmor/aare.py 2015-11-16 00:10:44.632187040 +0100
@@ -0,0 +1,83 @@
+# ----------------------------------------------------------------------
+# 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, 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.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 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))
+
+
+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:14:18.839452792 +0100
+++ utils/test/test-aare.py 2015-11-16 21:09:09.393489201 +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.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 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.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)
+
+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
--
Immer noch am Squid oder lieber Knöpfchen drücken und anstarren,
ob sich was tut? Vergiß es, der Computer sitzt am längeren Hebel
und ist, was solche Geschäfte angeht, ein Sturkopf.
[Helga Fischer in suse-linux]
More information about the AppArmor
mailing list