mirror of
https://gitlab.com/apparmor/apparmor.git
synced 2025-03-04 16:35:02 +01:00
Add 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. If log_event is given (which means handing over a raw path, not a regex), the given path is converted to a regex in convert_expression_to_aare(). (Also, the raw path is used in match().) BTW: 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. The patch also includes several tests. Acked-by: John Johansen <john.johansen@canonical.com>
This commit is contained in:
parent
6ee8cc6203
commit
441d3d2ae2
2 changed files with 191 additions and 2 deletions
83
utils/apparmor/aare.py
Normal file
83
utils/apparmor/aare.py
Normal file
|
@ -0,0 +1,83 @@
|
|||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2015 Christian Boltz <apparmor@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
|
|
@ -14,7 +14,8 @@ import unittest
|
|||
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 @@ class TestConvert_regexp(AATest):
|
|||
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 @@ class TestExamplesConvert_regexp(AATest):
|
|||
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__':
|
||||
|
|
Loading…
Add table
Reference in a new issue