#!/usr/bin/python3 # ---------------------------------------------------------------------- # Copyright (C) 2015 Christian Boltz # # 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 unittest from collections import namedtuple from apparmor.common import AppArmorBug, AppArmorException from apparmor.logparser import ReadLog from apparmor.aare import AARE from apparmor.rule.pivot_root import PivotRootRule, PivotRootRuleset from apparmor.translations import init_translation from common_test import AATest, setup_all_loops _ = init_translation() exp = namedtuple( 'exp', ('audit', 'allow_keyword', 'deny', 'comment', 'oldroot', 'all_oldroots', 'newroot', 'all_newroots', 'profile_name', 'all_profile_names'), ) # # --- tests for single PivotRootRule --- # class PivotRootTest(AATest): def _compare_obj(self, obj, expected): self.assertEqual(expected.audit, obj.audit) self.assertEqual(expected.allow_keyword, obj.allow_keyword) self.assertEqual(expected.deny, obj.deny) self.assertEqual(expected.comment, obj.comment) if type(obj.oldroot) is AARE: self.assertEqual(expected.oldroot, obj.oldroot.regex) else: self.assertEqual(expected.oldroot, obj.oldroot) self.assertEqual(expected.all_oldroots, obj.all_oldroots) if type(obj.newroot) is AARE: self.assertEqual(expected.newroot, obj.newroot.regex) else: self.assertEqual(expected.newroot, obj.newroot) self.assertEqual(expected.all_newroots, obj.all_newroots) if type(obj.profile_name) is AARE: self.assertEqual(expected.profile_name, obj.profile_name.regex) else: self.assertEqual(expected.profile_name, obj.profile_name) self.assertEqual(expected.all_profile_names, obj.all_profile_names) class PivotRootTestParse(PivotRootTest): tests = ( # PivotRootRule object audit allow deny comment oldroot all? newroot all? profile_name all? ('pivot_root,', exp(False, False, False, '', None, True, None, True, None, True)), ('pivot_root oldroot=/oldroot, # cmt', exp(False, False, False, ' # cmt', '/oldroot', False, None, True, None, True)), ('pivot_root oldroot=/oldroot /new/root, # cmt', exp(False, False, False, ' # cmt', '/oldroot', False, '/new/root', False, None, True)), ('pivot_root oldroot=/oldroot /new/root -> targetprof, # cmt', exp(False, False, False, ' # cmt', '/oldroot', False, '/new/root', False, 'targetprof', False)), ('pivot_root oldroot=/oldroot -> targetprof, # cmt', exp(False, False, False, ' # cmt', '/oldroot', False, None, True, 'targetprof', False)), ('pivot_root /new/root, # cmt', exp(False, False, False, ' # cmt', None, True, '/new/root', False, None, True)), ('pivot_root /new/root -> targetprof, # cmt', exp(False, False, False, ' # cmt', None, True, '/new/root', False, 'targetprof', False)), ('pivot_root -> targetprof, # cmt', exp(False, False, False, ' # cmt', None, True, None, True, 'targetprof', False)), ('pivot_root oldroot="/oldroot", # cmt', exp(False, False, False, ' # cmt', '/oldroot', False, None, True, None, True)), ('pivot_root "/new/root", # cmt', exp(False, False, False, ' # cmt', None, True, '/new/root', False, None, True)), ('pivot_root -> "targetprof", # cmt', exp(False, False, False, ' # cmt', None, True, None, True, 'targetprof', False)), ) def _run_test(self, rawrule, expected): self.assertTrue(PivotRootRule.match(rawrule)) obj = PivotRootRule.create_instance(rawrule) self.assertEqual(rawrule.strip(), obj.raw_rule) self._compare_obj(obj, expected) class PivotRootTestParseInvalid(PivotRootTest): tests = ( ('pivot_root foo,', AppArmorException), ('pivot_root foo bar,', AppArmorException), ('pivot_root oldroot= ,', AppArmorException), ('pivot_root -> ,', AppArmorException), ) def _run_test(self, rawrule, expected): self.assertTrue(PivotRootRule.match(rawrule)) # the above invalid rules still match the main regex! with self.assertRaises(expected): PivotRootRule.create_instance(rawrule) def test_invalid_rule_name(self): self.assertFalse(PivotRootRule.match('pivot_rootbeer,')) with self.assertRaises(AppArmorException): PivotRootRule.create_instance('pivot_rootbeer,') class PivotRootTestParseFromLog(PivotRootTest): def test_pivot_root_from_log(self): parser = ReadLog('', '', '') event = 'type=AVC msg=audit(1409700678.384:547594): apparmor="DENIED" operation="pivotroot" profile="/home/ubuntu/bzr/apparmor/tests/regression/apparmor/pivot_root" name="/tmp/sdtest.21082-7446-EeefO6/new_root/" pid=21162 comm="pivot_root" srcname="/tmp/sdtest.21082-7446-EeefO6/new_root/put_old/"' parsed_event = parser.parse_event(event) self.assertEqual(parsed_event, { 'request_mask': None, 'denied_mask': None, 'error_code': 0, 'magic_token': 0, 'parent': 0, 'profile': '/home/ubuntu/bzr/apparmor/tests/regression/apparmor/pivot_root', 'operation': 'pivotroot', 'resource': None, 'info': None, 'aamode': 'REJECTING', 'time': 1409700678, 'active_hat': None, 'pid': 21162, 'task': 0, 'attr': None, 'name2': None, 'src_name': '/tmp/sdtest.21082-7446-EeefO6/new_root/put_old/', 'name': '/tmp/sdtest.21082-7446-EeefO6/new_root/', 'family': None, 'protocol': None, 'sock_type': None, 'class': None, }) obj = PivotRootRule(parsed_event['src_name'], parsed_event['name'], PivotRootRule.ALL, log_event=parsed_event) # audit allow deny comment oldroot all? newroot all? target all? expected = exp(False, False, False, '', '/tmp/sdtest.21082-7446-EeefO6/new_root/put_old/', False, '/tmp/sdtest.21082-7446-EeefO6/new_root/', False, None, True) self._compare_obj(obj, expected) self.assertEqual( obj.get_raw(1), ' pivot_root oldroot=/tmp/sdtest.21082-7446-EeefO6/new_root/put_old/ /tmp/sdtest.21082-7446-EeefO6/new_root/,') class PivotRootFromInit(PivotRootTest): tests = ( # PivotRootRule object audit allow deny comment oldroot all? newroot all? profile_name all? (PivotRootRule('/oldroot', '/new/root', 'some_profile', deny=True), exp(False, False, True, '', '/oldroot', False, '/new/root', False, 'some_profile', False)), (PivotRootRule('/oldroot', '/new/root', PivotRootRule.ALL, deny=True), exp(False, False, True, '', '/oldroot', False, '/new/root', False, None, True)), (PivotRootRule('/oldroot', PivotRootRule.ALL, '/someprofile', deny=True), exp(False, False, True, '', '/oldroot', False, None, True, '/someprofile', False)), (PivotRootRule(PivotRootRule.ALL, '/new/root', '/someprofile', deny=True), exp(False, False, True, '', None, True, '/new/root', False, '/someprofile', False)), (PivotRootRule('/oldroot', PivotRootRule.ALL, PivotRootRule.ALL, deny=True), exp(False, False, True, '', '/oldroot', False, None, True, None, True)), (PivotRootRule(PivotRootRule.ALL, '/new/root', PivotRootRule.ALL, deny=True), exp(False, False, True, '', None, True, '/new/root', False, None, True)), (PivotRootRule(PivotRootRule.ALL, PivotRootRule.ALL, 'some_profile', deny=True), exp(False, False, True, '', None, True, None, True, 'some_profile', False)), (PivotRootRule(PivotRootRule.ALL, PivotRootRule.ALL, PivotRootRule.ALL, deny=True), exp(False, False, True, '', None, True, None, True, None, True)), ) def _run_test(self, obj, expected): self._compare_obj(obj, expected) class InvalidPivotRootInit(AATest): tests = ( # (init params, expected exception) (('', '/foo', 'bar'), AppArmorBug), # empty oldroot (('/old', '', 'bar'), AppArmorBug), # empty newroot (('/old', '/foo', '' ), AppArmorBug), # empty targetprof # noqa: E202 (('old', '/foo', 'bar'), AppArmorException), # oldroot is not a path (('/old', 'foo', 'bar'), AppArmorException), # newroot is not a path ((None, '/foo', 'bar'), AppArmorBug), # wrong type (('/old', None, 'bar'), AppArmorBug), # (('/old', '/foo', None ), AppArmorBug), # noqa: E202 ((dict(), '/foo', 'bar'), AppArmorBug), # wrong type (('/old', dict(), 'bar'), AppArmorBug), # (('/old', '/foo', dict()), AppArmorBug), # ) def _run_test(self, params, expected): with self.assertRaises(expected): PivotRootRule(*params) def test_missing_params_1(self): with self.assertRaises(TypeError): PivotRootRule() def test_missing_params_2(self): with self.assertRaises(TypeError): PivotRootRule('/foo') def test_missing_params_3(self): with self.assertRaises(TypeError): PivotRootRule('/foo', '/bar') class InvalidPivotRootTest(AATest): def _check_invalid_rawrule(self, rawrule): obj = None self.assertFalse(PivotRootRule.match(rawrule)) with self.assertRaises(AppArmorException): obj = PivotRootRule.create_instance(rawrule) self.assertIsNone(obj, 'PivotRootRule handed back an object unexpectedly') def test_invalid_pivot_root_missing_comma(self): self._check_invalid_rawrule('pivot_root') # missing comma def test_invalid_non_PivotRootRule(self): self._check_invalid_rawrule('dbus,') # not a pivot_root rule def test_empty_data_1(self): obj = PivotRootRule('/foo', '/bar', 'prof') obj.oldroot = '' # no oldroot set, and ALL not set with self.assertRaises(AppArmorBug): obj.get_clean(1) def test_empty_data_2(self): obj = PivotRootRule('/foo', '/bar', 'prof') obj.newroot = '' # no newroot set, and ALL not set with self.assertRaises(AppArmorBug): obj.get_clean(1) def test_empty_data_3(self): obj = PivotRootRule('/foo', '/bar', 'prof') obj.profile_name = '' # no profile_name set, and ALL not set with self.assertRaises(AppArmorBug): obj.get_clean(1) class WritePivotRootTestAATest(AATest): def _run_test(self, rawrule, expected): self.assertTrue(PivotRootRule.match(rawrule)) obj = PivotRootRule.create_instance(rawrule) clean = obj.get_clean() raw = obj.get_raw() self.assertEqual(expected.strip(), clean, 'unexpected clean rule') self.assertEqual(rawrule.strip(), raw, 'unexpected raw rule') tests = ( # raw rule clean rule ('pivot_root,', 'pivot_root,'), (' pivot_root , # foo ', 'pivot_root, # foo'), (' audit pivot_root /foo,', 'audit pivot_root /foo,'), (' deny pivot_root /foo ,# foo bar', 'deny pivot_root /foo, # foo bar'), (' deny pivot_root "/foo" ,# foo bar', 'deny pivot_root /foo, # foo bar'), (' allow pivot_root ,# foo bar', 'allow pivot_root, # foo bar'), (' pivot_root oldroot=/old , # foo ', 'pivot_root oldroot=/old, # foo'), (' pivot_root oldroot="/old" , # foo ', 'pivot_root oldroot=/old, # foo'), (' pivot_root oldroot=/old -> some_profile , ', 'pivot_root oldroot=/old -> some_profile,'), (' pivot_root oldroot=/old /new -> some_profile , ', 'pivot_root oldroot=/old /new -> some_profile,'), ) def test_write_manually(self): obj = PivotRootRule('/old', '/new', 'target', allow_keyword=True) expected = ' allow pivot_root oldroot=/old /new -> target,' self.assertEqual(expected, obj.get_clean(2), 'unexpected clean rule') self.assertEqual(expected, obj.get_raw(2), 'unexpected raw rule') class PivotRootCoveredTest(AATest): def _run_test(self, param, expected): obj = PivotRootRule.create_instance(self.rule) check_obj = PivotRootRule.create_instance(param) self.assertTrue(PivotRootRule.match(param)) self.assertEqual(obj.is_equal(check_obj), expected[0], 'Mismatch in is_equal, expected {}'.format(expected[0])) self.assertEqual(obj.is_equal(check_obj, True), expected[1], 'Mismatch in is_equal/strict, expected {}'.format(expected[1])) self.assertEqual(obj.is_covered(check_obj), expected[2], 'Mismatch in is_covered, expected {}'.format(expected[2])) self.assertEqual(obj.is_covered(check_obj, True, True), expected[3], 'Mismatch in is_covered/exact, expected {}'.format(expected[3])) class PivotRootCoveredTest_01(PivotRootCoveredTest): rule = 'pivot_root /new,' tests = ( # rule equal strict equal covered covered exact ('pivot_root,', (False, False, False, False)), ('pivot_root /n*,', (False, False, False, False)), ('pivot_root oldroot=/old,', (False, False, False, False)), ('pivot_root /new,', (True, False, True, True)), ('pivot_root -> target,', (False, False, False, False)), ('pivot_root oldroot=/old /new,', (False, False, True, True)), ('pivot_root /new -> target,', (False, False, True, True)), ('pivot_root oldroot=/old -> target,', (False, False, False, False)), ('pivot_root oldroot=/old /new -> target,', (False, False, True, True)), ) class PivotRootCoveredTest_02(PivotRootCoveredTest): rule = 'audit pivot_root oldroot=/ol*,' tests = ( # rule equal strict equal covered covered exact ('audit pivot_root,', (False, False, False, False)), ('audit pivot_root oldroot=/ol*,', (True, True, True, True)), ('audit pivot_root oldroot=/old,', (False, False, True, True)), ('audit pivot_root /new,', (False, False, False, False)), ('audit pivot_root -> target,', (False, False, False, False)), ('audit pivot_root oldroot=/old /new,', (False, False, True, True)), ('audit pivot_root /new -> target,', (False, False, False, False)), ('audit pivot_root oldroot=/old -> target,', (False, False, True, True)), # covered exact - really? ('audit pivot_root oldroot=/old /new -> target,', (False, False, True, True)), # covered exact - really? ) class PivotRootCoveredTest_03(PivotRootCoveredTest): rule = 'pivot_root -> target,' tests = ( # rule equal strict equal covered covered exact ('pivot_root,', (False, False, False, False)), ('pivot_root oldroot=/ol*,', (False, False, False, False)), ('pivot_root oldroot=/old,', (False, False, False, False)), ('pivot_root /new,', (False, False, False, False)), ('pivot_root -> target,', (True, False, True, True)), ('pivot_root oldroot=/old /new,', (False, False, False, False)), ('pivot_root /new -> target,', (False, False, True, True)), ('pivot_root oldroot=/old -> target,', (False, False, True, True)), ('pivot_root oldroot=/old /new -> target,', (False, False, True, True)), ) class PivotRootCoveredTest_04(PivotRootCoveredTest): rule = 'deny pivot_root /foo,' tests = ( # rule equal strict equal covered covered exact (' deny pivot_root /foo,', (True, True, True, True)), ('audit deny pivot_root /foo,', (False, False, False, False)), (' pivot_root /foo,', (False, False, False, False)), # XXX should covered be true here? (' deny pivot_root /bar,', (False, False, False, False)), (' deny pivot_root,', (False, False, False, False)), ) class PivotRootCoveredTest_Invalid(AATest): # TODO: should this be detected? # def test_borked_obj_is_covered_1(self): # obj = PivotRootRule.create_instance('pivot_root oldroot=/old /new -> target,') # testobj = PivotRootRule('/old', '/foo', 'targetprof') # testobj.oldrooot = None # with self.assertRaises(AppArmorBug): # obj.is_covered(testobj) def test_borked_obj_is_covered_2(self): obj = PivotRootRule.create_instance('pivot_root oldroot=/old /new -> target,') testobj = PivotRootRule('/old', '/foo', 'targetprof') testobj.newroot = '' with self.assertRaises(AppArmorBug): obj.is_covered(testobj) # def test_borked_obj_is_covered_3(self): # TODO: should this be detected? # obj = PivotRootRule.create_instance('pivot_root oldroot=/old /new -> target,') # testobj = PivotRootRule('/old', '/foo', 'targetprof') # testobj.profile_name = '' # with self.assertRaises(AppArmorBug): # obj.is_covered(testobj) def test_invalid_is_covered(self): raw_rule = 'pivot_root oldroot=/old /new -> target,' class SomeOtherClass(PivotRootRule): pass obj = PivotRootRule.create_instance(raw_rule) testobj = SomeOtherClass.create_instance(raw_rule) # different type with self.assertRaises(AppArmorBug): obj.is_covered(testobj) def test_invalid_is_equal_1(self): raw_rule = 'pivot_root oldroot=/old /new -> target,' class SomeOtherClass(PivotRootRule): pass obj = PivotRootRule.create_instance(raw_rule) testobj = SomeOtherClass.create_instance(raw_rule) # different type with self.assertRaises(AppArmorBug): obj.is_equal(testobj) # def test_invalid_is_equal_2(self): # TODO: should this be detected? # obj = PivotRootRule.create_instance('pivot_root oldroot=/old /new -> target,') # testobj = PivotRootRule.create_instance('pivot_root oldroot=/old /new -> target,') # testobj.all_oldroots = False # make testobj invalid (should trigger exception in _is_equal_aare()) # with self.assertRaises(AppArmorBug): # obj.is_equal(testobj) class PivotRootLogprofHeaderTest(AATest): tests = ( ('pivot_root,', [ _('Old root'), _('ALL'), _('New root'), _('ALL'), _('Target profile'), _('ALL')]), # noqa: E201 ('pivot_root oldroot=/old,', [ _('Old root'), '/old', _('New root'), _('ALL'), _('Target profile'), _('ALL')]), # noqa: E201 ('deny pivot_root,', [_('Qualifier'), 'deny', _('Old root'), _('ALL'), _('New root'), _('ALL'), _('Target profile'), _('ALL')]), ('allow pivot_root oldroot=/old,', [_('Qualifier'), 'allow', _('Old root'), '/old', _('New root'), _('ALL'), _('Target profile'), _('ALL')]), ('audit pivot_root /new,', [_('Qualifier'), 'audit', _('Old root'), _('ALL'), _('New root'), '/new', _('Target profile'), _('ALL')]), ('audit deny pivot_root /new -> target,', [_('Qualifier'), 'audit deny', _('Old root'), _('ALL'), _('New root'), '/new', _('Target profile'), 'target']), ('pivot_root oldroot=/old /new -> target,', [ _('Old root'), '/old', _('New root'), '/new', _('Target profile'), 'target']), # noqa: E201 ) def _run_test(self, params, expected): obj = PivotRootRule.create_instance(params) self.assertEqual(obj.logprof_header(), expected) class PivotRootEditHeaderTest(AATest): def _run_test(self, params, expected): rule_obj = PivotRootRule.create_instance(params) self.assertEqual(rule_obj.can_edit, True) prompt, path_to_edit = rule_obj.edit_header() self.assertEqual(path_to_edit, expected) tests = ( ('pivot_root oldroot=/old /foo/bar/baz -> target,', '/foo/bar/baz'), ('pivot_root /foo/**/baz,', '/foo/**/baz'), ('pivot_root /foo/** -> /bar,', '/foo/**'), ) def test_edit_header_bare_pivot_root(self): rule_obj = PivotRootRule.create_instance('pivot_root,') self.assertEqual(rule_obj.can_edit, False) with self.assertRaises(AppArmorBug): rule_obj.edit_header() class PivotRootValidateAndStoreEditTest(AATest): def _run_test(self, params, expected): rule_obj = PivotRootRule('/old/', '/foo/bar/baz', 'target', log_event=True) self.assertEqual(rule_obj.validate_edit(params), expected) rule_obj.store_edit(params) self.assertEqual(rule_obj.get_raw(), 'pivot_root oldroot=/old/ ' + params + ' -> target,') tests = ( # edited path match ('/foo/bar/baz', True), ('/foo/bar/*', True), ('/foo/bar/???', True), ('/foo/xy**', False), ('/foo/bar/baz/', False), ) def test_validate_not_a_path(self): rule_obj = PivotRootRule.create_instance('pivot_root /foo/bar/baz,') with self.assertRaises(AppArmorException): rule_obj.validate_edit('foo/bar/baz') with self.assertRaises(AppArmorException): rule_obj.store_edit('foo/bar/baz') def test_validate_edit_bare_pivot_root(self): rule_obj = PivotRootRule.create_instance('pivot_root,') self.assertEqual(rule_obj.can_edit, False) with self.assertRaises(AppArmorBug): rule_obj.validate_edit('/foo/bar') with self.assertRaises(AppArmorBug): rule_obj.store_edit('/foo/bar') # --- tests for PivotRootRuleset --- # class PivotRootRulesTest(AATest): def test_empty_ruleset(self): ruleset = PivotRootRuleset() ruleset_2 = PivotRootRuleset() self.assertEqual([], ruleset.get_raw(2)) self.assertEqual([], ruleset.get_clean(2)) self.assertEqual([], ruleset_2.get_raw(2)) self.assertEqual([], ruleset_2.get_clean(2)) # test __repr__() for empty ruleset self.assertEqual(str(ruleset), '') def test_ruleset_1(self): ruleset = PivotRootRuleset() rules = ( 'pivot_root oldroot=/foo,', 'pivot_root /new,', ) expected_raw = [ 'pivot_root oldroot=/foo,', 'pivot_root /new,', '', ] expected_clean = [ 'pivot_root /new,', 'pivot_root oldroot=/foo,', '', ] for rule in rules: ruleset.add(PivotRootRule.create_instance(rule)) self.assertEqual(expected_raw, ruleset.get_raw()) self.assertEqual(expected_clean, ruleset.get_clean()) # test __repr__() for non-empty ruleset self.assertEqual( str(ruleset), '\n pivot_root oldroot=/foo,\n pivot_root /new,\n') class PivotRootGlobTestAATest(AATest): def test_glob(self): glob_list = [( 'pivot_root /foo/bar,', 'pivot_root /foo/*,', 'pivot_root /**,', )] for globs in glob_list: for i in range(len(globs) - 1): rule = PivotRootRule.create_instance(globs[i]) rule.glob() self.assertEqual(rule.get_clean(), globs[i + 1]) def test_glob_all(self): glob_list = [( 'pivot_root,', 'pivot_root,', )] for globs in glob_list: for i in range(len(globs) - 1): rule = PivotRootRule.create_instance(globs[i]) rule.glob() self.assertEqual(rule.get_clean(), globs[i + 1]) # def test_glob_ext(self): # # rule = PivotRootRule.create_instance('pivot_root /foo/bar,') # with self.assertRaises(NotImplementedError): # # get_glob_ext is not available for pivot_root rules # self.ruleset.get_glob_ext('pivot_root /foo,') # class PivotRootDeleteTestAATest(AATest): # pass setup_all_loops(__name__) if __name__ == '__main__': unittest.main(verbosity=1)