On Thu, Mar 06, 2014 at 11:02:24PM -0800, Steve Beattie wrote: > Subject: utils: add simple parsing of multi-line rules [v2] > > D-Bus rules in particular seem to get written as multi-line rules. This > patch adds very simple hackish support for multiple lines. Essentially, > what it does is if the parsing of a line doesn't match anything and > falls all the way through, it saves the line and prepends it to the next > line that occurs in the profile, but *only* if the line does not have a > trailing comma to indicate the end of a rule. If the trailing comma > exists, then it assumes that it's a rule that it doesn't understand and > aborts. > > With this patch, the simpler tools (aa-enforce, aa-complain, etc.) can > parse policies containing multi-line rules to an extent and continue to > function correctly. Again, aa-logprof and aa-genprof may have issues on > the writing back of profiles, so some assistance testing here would be > appreciated. > > Some testcases are added to exercise the regex that looks for a rule > with a trailing comma but can still handle rules that have (,) or {,} > in them. > > Patch history: > v1 - initial version > v2 - simplify and rearrange rule-ending comma search regex, since > we only care about the trailing comma > - add a new regex to search for trailing comments to filter out > - simplify reset of lastline variable > - restructure tests into a new script, and add more tests > > Signed-off-by: Steve Beattie <st...@nxnw.org>
Thanks for the refresh, the broken-apart and named regexps are so much easier to follow. The test overhaul is impressive too, i especially love that adding new entries in the future will be pretty straightforward. Acked-by: Seth Arnold <seth.arn...@canonical.com> Thanks! > --- > utils/apparmor/aa.py | 22 +++++++ > utils/test/test-regex_matches.py | 109 > +++++++++++++++++++++++++++++++++++++++ > 2 files changed, 130 insertions(+), 1 deletion(-) > > Index: b/utils/apparmor/aa.py > =================================================================== > --- a/utils/apparmor/aa.py > +++ b/utils/apparmor/aa.py > @@ -2615,7 +2615,15 @@ RE_PROFILE_CHANGE_HAT = re.compile('^\s* > RE_PROFILE_HAT_DEF = > re.compile('^\s*\^(\"??.+?\"??)\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$') > RE_NETWORK_FAMILY_TYPE = re.compile('\s+(\S+)\s+(\S+)\s*,$') > RE_NETWORK_FAMILY = re.compile('\s+(\S+)\s*,$') > -RE_PROFILE_DBUS = > re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?(dbus[^#]*)\s*(#.*)?$') > +RE_PROFILE_DBUS = > re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?(dbus[^#]*\s*,)\s*(#.*)?$') > + > +# match anything that's not " or #, or matching quotes with anything except > quotes inside > +__re_no_or_quoted_hash = '([^#"]|"[^"]*")*' > + > +RE_RULE_HAS_COMMA = re.compile('^' + __re_no_or_quoted_hash + > + ',\s*(#.*)?$') # match comma plus any trailing comment > +RE_HAS_COMMENT_SPLIT = re.compile('^(?P<not_comment>' + > __re_no_or_quoted_hash + ')' + # store in 'not_comment' group > + '(?P<comment>#.*)$') # match trailing comment and store in 'comment' > group > > def parse_profile_data(data, file, do_include): > profile_data = hasher() > @@ -2625,6 +2633,7 @@ def parse_profile_data(data, file, do_in > repo_data = None > parsed_profiles = [] > initial_comment = '' > + lastline = None > > if do_include: > profile = file > @@ -2633,6 +2642,10 @@ def parse_profile_data(data, file, do_in > line = line.strip() > if not line: > continue > + # we're dealing with a multiline statement > + if lastline: > + line = '%s %s' % (lastline, line) > + lastline = None > # Starting line of a profile > if RE_PROFILE_START.search(line): > matches = RE_PROFILE_START.search(line).groups() > @@ -3007,6 +3020,13 @@ def parse_profile_data(data, file, do_in > else: > initial_comment = initial_comment + line + '\n' > > + elif not RE_RULE_HAS_COMMA.search(line): > + # Bah, line continues on to the next line > + if RE_HAS_COMMENT_SPLIT.search(line): > + # filter trailing comments > + lastline = > RE_HAS_COMMENT_SPLIT.search(line).group('not_comment') > + else: > + lastline = line > else: > raise AppArmorException(_('Syntax Error: Unknown line found in > file: %s line: %s') % (file, lineno + 1)) > > Index: b/utils/test/test-regex_matches.py > =================================================================== > --- /dev/null > +++ b/utils/test/test-regex_matches.py > @@ -0,0 +1,109 @@ > +#! /usr/bin/env python > +# ------------------------------------------------------------------ > +# > +# Copyright (C) 2014 Canonical Ltd. > +# > +# 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 published by the Free Software Foundation. > +# > +# ------------------------------------------------------------------ > + > +import apparmor.aa as aa > +import unittest > + > + > +class AARegexHasComma(unittest.TestCase): > + '''Tests for apparmor.aa.RE_RULE_HAS_COMMA''' > + > + def _check(self, line, expected=True): > + result = aa.RE_RULE_HAS_COMMA.search(line) > + if expected: > + self.assertTrue(result, 'Couldn\'t find a comma in "%s"' % line) > + else: > + self.assertEqual(None, result, 'Found an unexpected comma in > "%s"' % line) > + > +regex_has_comma_testcases = [ > + ('dbus send%s', 'simple'), > + ('dbus (r, w, bind, eavesdrop)%s', 'embedded parens 01'), > + ('dbus (r, w,, bind, eavesdrop) %s', 'embedded parens 02'), > + ('dbus (r, w,, ) %s', 'embedded parens 03'), > + ('dbus () %s', 'empty parens'), > + > ('member={Hello,AddMatch,RemoveMatch,GetNameOwner,NameHasOwner,StartServiceByName} > %s ', 'embedded curly braces 01'), > + ('member={Hello,,,,,,AddMatch,,,NameHasOwner,StartServiceByName} %s ', > 'embedded curly braces 02'), > + ('member={,Hello,,,,,,AddMatch,,,NameHasOwner,} %s ', 'embedded curly > braces 03'), > + ('member={} %s ', 'empty curly braces'), > + ('dbus send%s# this is a comment', 'comment 01'), > + ('dbus send%s# this is a comment,', 'comment 02'), > + ('audit "/tmp/foo, bar" rw%s', 'quotes 01'), > + ('audit "/tmp/foo, bar" rw%s # comment', 'quotes 02'), > + ('audit "/tmp/foo, # bar" rw%s', 'comment embedded in quote 01'), > + ('audit "/tmp/foo, # bar" rw%s # comment', 'comment embedded in quote > 02'), > + # the following fail due to inadequacies in the regex > + # ('dbus (r, w, %s', 'incomplete dbus action'), > + # ('member={Hello,AddMatch,RemoveMatch, %s', 'incomplete {} regex'), # > also invalid policy > +] > + > +def setup_has_comma_testcases(): > + i = 0 > + for (test_string, description) in regex_has_comma_testcases: > + i += 1 > + def stub_test_comma(self, test_string=test_string): > + self._check(test_string % ',') > + def stub_test_no_comma(self, test_string=test_string): > + self._check(test_string % ' ', False) > + stub_test_comma.__doc__ = "test %s (w/comma)" % (description) > + stub_test_no_comma.__doc__ = "test %s (no comma)" % (description) > + setattr(AARegexHasComma, 'test_comma_%d' % (i), stub_test_comma) > + setattr(AARegexHasComma, 'test_no_comma_%d' % (i), > stub_test_no_comma) > + > +class AARegexSplitComment(unittest.TestCase): > + '''Tests for RE_HAS_COMMENT_SPLIT''' > + > + def _check(self, line, expected, comment=None, not_comment=None): > + result = aa.RE_HAS_COMMENT_SPLIT.search(line) > + if expected: > + self.assertTrue(result, 'Couldn\'t find a comment in "%s"' % > line) > + self.assertEqual(result.group('comment'), comment, 'Expected > comment "%s", got "%s"' > + % (comment, result.group('comment'))) > + self.assertEqual(result.group('not_comment'), not_comment, > 'Expected not comment "%s", got "%s"' > + % (not_comment, result.group('not_comment'))) > + else: > + self.assertEqual(None, result, 'Found an unexpected comment "%s" > in "%s"' > + % ("" if result is None else > result.group('comment'), line )) > + > +# Tuples of (string, expected result), where expected result is False if > +# the string should not be considered as having a comment, or a second > +# tuple of the not comment and comment sections split apart > +regex_split_comment_testcases = [ > + ('dbus send # this is a comment', ('dbus send ', '# this is a comment')), > + ('dbus send member=no_comment', False), > + ('dbus send member=no_comment, ', False), > + ('audit "/tmp/foo, # bar" rw', False), > + ('audit "/tmp/foo, # bar" rw # comment', ('audit "/tmp/foo, # bar" rw ', > '# comment')), > +] > + > +def setup_split_comment_testcases(): > + i = 0 > + for (test_string, result) in regex_split_comment_testcases: > + i += 1 > + def stub_test(self, test_string=test_string, result=result): > + if result is False: > + self._check(test_string, False) > + else: > + self._check(test_string, True, not_comment=result[0], > comment=result[1]) > + stub_test.__doc__ = "test '%s'" % (test_string) > + setattr(AARegexSplitComment, 'test_split_comment_%d' % (i), > stub_test) > + > +if __name__ == '__main__': > + verbosity = 2 > + > + setup_has_comma_testcases() > + setup_split_comment_testcases() > + > + test_suite = unittest.TestSuite() > + > test_suite.addTest(unittest.TestLoader().loadTestsFromTestCase(AARegexHasComma)) > + > test_suite.addTest(unittest.TestLoader().loadTestsFromTestCase(AARegexSplitComment)) > + result = unittest.TextTestRunner(verbosity=verbosity).run(test_suite) > + if not result.wasSuccessful(): > + exit(1) > -- > AppArmor mailing list > AppArmor@lists.ubuntu.com > Modify settings or unsubscribe at: > https://lists.ubuntu.com/mailman/listinfo/apparmor
signature.asc
Description: Digital signature
-- AppArmor mailing list AppArmor@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/apparmor